create-better-t-stack
Version:
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
1,456 lines (1,427 loc) • 155 kB
JavaScript
#!/usr/bin/env node
import path from "node:path";
import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, password, select, spinner, text } from "@clack/prompts";
import consola, { consola as consola$1 } from "consola";
import fs from "fs-extra";
import pc from "picocolors";
import { createCli, trpcServer, zod } from "trpc-cli";
import { fileURLToPath } from "node:url";
import { $, execa } from "execa";
import os from "node:os";
import { globby } from "globby";
import handlebars from "handlebars";
import { z } from "zod";
import { PostHog } from "posthog-node";
import gradient from "gradient-string";
//#region src/utils/get-package-manager.ts
const getUserPkgManager = () => {
const userAgent = process.env.npm_config_user_agent;
if (userAgent?.startsWith("pnpm")) return "pnpm";
if (userAgent?.startsWith("bun")) return "bun";
return "npm";
};
//#endregion
//#region src/constants.ts
const __filename = fileURLToPath(import.meta.url);
const distPath = path.dirname(__filename);
const PKG_ROOT = path.join(distPath, "../");
const DEFAULT_CONFIG = {
projectName: "my-better-t-app",
projectDir: path.resolve(process.cwd(), "my-better-t-app"),
relativePath: "my-better-t-app",
frontend: ["tanstack-router"],
database: "sqlite",
orm: "drizzle",
auth: true,
addons: ["turborepo"],
examples: [],
git: true,
packageManager: getUserPkgManager(),
install: true,
dbSetup: "none",
backend: "hono",
runtime: "bun",
api: "trpc"
};
const dependencyVersionMap = {
"better-auth": "^1.2.9",
"@better-auth/expo": "^1.2.9",
"drizzle-orm": "^0.38.4",
"drizzle-kit": "^0.30.5",
"@libsql/client": "^0.14.0",
pg: "^8.14.1",
"@types/pg": "^8.11.11",
mysql2: "^3.14.0",
"@prisma/client": "^6.9.0",
prisma: "^6.9.0",
mongoose: "^8.14.0",
"vite-plugin-pwa": "^0.21.2",
"@vite-pwa/assets-generator": "^0.2.6",
"@tauri-apps/cli": "^2.4.0",
"@biomejs/biome": "^2.0.0",
husky: "^9.1.7",
"lint-staged": "^15.5.0",
"@hono/node-server": "^1.14.0",
tsx: "^4.19.2",
"@types/node": "^22.13.11",
"@types/bun": "^1.2.6",
"@elysiajs/node": "^1.2.6",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/trpc": "^1.1.0",
elysia: "^1.2.25",
"@hono/trpc-server": "^0.3.4",
hono: "^4.7.6",
cors: "^2.8.5",
express: "^5.1.0",
"@types/express": "^5.0.1",
"@types/cors": "^2.8.17",
fastify: "^5.3.3",
"@fastify/cors": "^11.0.1",
turbo: "^2.5.4",
ai: "^4.3.16",
"@ai-sdk/google": "^1.2.3",
"@ai-sdk/vue": "^1.2.8",
"@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",
"@trpc/tanstack-react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
"@trpc/client": "^11.0.0",
convex: "^1.23.0",
"@convex-dev/react-query": "^0.0.0-alpha.8",
"convex-svelte": "^0.0.11",
"@tanstack/svelte-query": "^5.74.4",
"@tanstack/react-query-devtools": "^5.80.5",
"@tanstack/react-query": "^5.80.5",
"@tanstack/solid-query": "^5.75.0",
"@tanstack/solid-query-devtools": "^5.75.0",
wrangler: "^4.20.0"
};
//#endregion
//#region src/utils/add-package-deps.ts
const addPackageDependency = async (opts) => {
const { dependencies = [], devDependencies = [], projectDir } = opts;
const pkgJsonPath = path.join(projectDir, "package.json");
const pkgJson = await fs.readJson(pkgJsonPath);
if (!pkgJson.dependencies) pkgJson.dependencies = {};
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
for (const pkgName of dependencies) {
const version = dependencyVersionMap[pkgName];
if (version) pkgJson.dependencies[pkgName] = version;
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
}
for (const pkgName of devDependencies) {
const version = dependencyVersionMap[pkgName];
if (version) pkgJson.devDependencies[pkgName] = version;
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
}
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
};
//#endregion
//#region src/utils/get-package-execution-command.ts
/**
* Returns the appropriate command for running a package without installing it globally,
* based on the selected package manager.
*
* @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
* @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate --schema=./prisma/schema.prisma").
* @returns The full command string (e.g., "npx prisma generate --schema=./prisma/schema.prisma").
*/
function getPackageExecutionCommand(packageManager, commandWithArgs) {
switch (packageManager) {
case "pnpm": return `pnpm dlx ${commandWithArgs}`;
case "bun": return `bunx ${commandWithArgs}`;
default: return `npx ${commandWithArgs}`;
}
}
//#endregion
//#region src/helpers/setup/starlight-setup.ts
async function setupStarlight(config) {
const { packageManager, projectDir } = config;
const s = spinner();
try {
s.start("Setting up Starlight docs...");
const starlightArgs = [
"docs",
"--template",
"starlight",
"--no-install",
"--add",
"tailwind",
"--no-git",
"--skip-houston"
];
const starlightArgsString = starlightArgs.join(" ");
const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
await execa(starlightInitCommand, {
cwd: path.join(projectDir, "apps"),
env: { CI: "true" },
shell: true
});
s.stop("Starlight docs setup successfully!");
} catch (error) {
s.stop(pc.red("Failed to set up Starlight docs"));
if (error instanceof Error) consola.error(pc.red(error.message));
}
}
//#endregion
//#region src/helpers/setup/tauri-setup.ts
async function setupTauri(config) {
const { packageManager, frontend, projectDir } = config;
const s = spinner();
const clientPackageDir = path.join(projectDir, "apps/web");
if (!await fs.pathExists(clientPackageDir)) return;
try {
s.start("Setting up Tauri desktop app support...");
await addPackageDependency({
devDependencies: ["@tauri-apps/cli"],
projectDir: clientPackageDir
});
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
if (await fs.pathExists(clientPackageJsonPath)) {
const packageJson = await fs.readJson(clientPackageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
tauri: "tauri",
"desktop:dev": "tauri dev",
"desktop:build": "tauri build"
};
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
}
const _hasTanstackRouter = frontend.includes("tanstack-router");
const hasReactRouter = frontend.includes("react-router");
const hasNuxt = frontend.includes("nuxt");
const hasSvelte = frontend.includes("svelte");
const _hasSolid = frontend.includes("solid");
const hasNext = frontend.includes("next");
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
const tauriArgs = [
"init",
`--app-name=${path.basename(projectDir)}`,
`--window-title=${path.basename(projectDir)}`,
`--frontend-dist=${frontendDist}`,
`--dev-url=${devUrl}`,
`--before-dev-command=\"${packageManager} run dev\"`,
`--before-build-command=\"${packageManager} run build\"`
];
const tauriArgsString = tauriArgs.join(" ");
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
await execa(tauriInitCommand, {
cwd: clientPackageDir,
env: { CI: "true" },
shell: true
});
s.stop("Tauri desktop app support configured successfully!");
} catch (error) {
s.stop(pc.red("Failed to set up Tauri"));
if (error instanceof Error) consola$1.error(pc.red(error.message));
}
}
//#endregion
//#region src/helpers/setup/addons-setup.ts
async function setupAddons(config) {
const { addons, frontend, projectDir } = config;
const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
const hasNuxtFrontend = frontend.includes("nuxt");
const hasSvelteFrontend = frontend.includes("svelte");
const hasSolidFrontend = frontend.includes("solid");
const hasNextFrontend = frontend.includes("next");
if (addons.includes("turborepo")) await addPackageDependency({
devDependencies: ["turbo"],
projectDir
});
if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) await setupPwa(projectDir, frontend);
if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
if (addons.includes("biome")) await setupBiome(projectDir);
if (addons.includes("husky")) await setupHusky(projectDir);
if (addons.includes("starlight")) await setupStarlight(config);
}
function getWebAppDir(projectDir, frontends) {
if (frontends.some((f) => [
"react-router",
"tanstack-router",
"nuxt",
"svelte",
"solid"
].includes(f))) return path.join(projectDir, "apps/web");
return path.join(projectDir, "apps/web");
}
async function setupBiome(projectDir) {
await addPackageDependency({
devDependencies: ["@biomejs/biome"],
projectDir
});
const packageJsonPath = path.join(projectDir, "package.json");
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
check: "biome check --write ."
};
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
}
async function setupHusky(projectDir) {
await addPackageDependency({
devDependencies: ["husky", "lint-staged"],
projectDir
});
const packageJsonPath = path.join(projectDir, "package.json");
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
prepare: "husky"
};
packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
}
}
async function setupPwa(projectDir, frontends) {
const isCompatibleFrontend = frontends.some((f) => [
"react-router",
"tanstack-router",
"solid"
].includes(f));
if (!isCompatibleFrontend) return;
const clientPackageDir = getWebAppDir(projectDir, frontends);
if (!await fs.pathExists(clientPackageDir)) return;
await addPackageDependency({
dependencies: ["vite-plugin-pwa"],
devDependencies: ["@vite-pwa/assets-generator"],
projectDir: clientPackageDir
});
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
if (await fs.pathExists(clientPackageJsonPath)) {
const packageJson = await fs.readJson(clientPackageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
"generate-pwa-assets": "pwa-assets-generator"
};
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
}
}
//#endregion
//#region src/helpers/setup/api-setup.ts
async function setupApi(config) {
const { api, projectName, frontend, backend, packageManager, projectDir } = config;
const isConvex = backend === "convex";
const webDir = path.join(projectDir, "apps/web");
const nativeDir = path.join(projectDir, "apps/native");
const webDirExists = await fs.pathExists(webDir);
const nativeDirExists = await fs.pathExists(nativeDir);
const hasReactWeb = frontend.some((f) => [
"tanstack-router",
"react-router",
"tanstack-start",
"next"
].includes(f));
const hasNuxtWeb = frontend.includes("nuxt");
const hasSvelteWeb = frontend.includes("svelte");
const hasSolidWeb = frontend.includes("solid");
if (!isConvex && api !== "none") {
const serverDir = path.join(projectDir, "apps/server");
const serverDirExists = await fs.pathExists(serverDir);
if (serverDirExists) {
if (api === "orpc") await addPackageDependency({
dependencies: ["@orpc/server", "@orpc/client"],
projectDir: serverDir
});
else if (api === "trpc") {
await addPackageDependency({
dependencies: ["@trpc/server", "@trpc/client"],
projectDir: serverDir
});
if (config.backend === "hono") await addPackageDependency({
dependencies: ["@hono/trpc-server"],
projectDir: serverDir
});
else if (config.backend === "elysia") await addPackageDependency({
dependencies: ["@elysiajs/trpc"],
projectDir: serverDir
});
}
}
if (webDirExists) {
if (hasReactWeb) {
if (api === "orpc") await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server"
],
projectDir: webDir
});
else if (api === "trpc") await addPackageDependency({
dependencies: [
"@trpc/tanstack-react-query",
"@trpc/client",
"@trpc/server"
],
projectDir: webDir
});
} else if (hasNuxtWeb) {
if (api === "orpc") await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server"
],
projectDir: webDir
});
} else if (hasSvelteWeb) {
if (api === "orpc") await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
"@tanstack/svelte-query"
],
projectDir: webDir
});
} else if (hasSolidWeb) {
if (api === "orpc") await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
"@tanstack/solid-query"
],
projectDir: webDir
});
}
}
if (nativeDirExists) {
if (api === "trpc") await addPackageDependency({
dependencies: [
"@trpc/tanstack-react-query",
"@trpc/client",
"@trpc/server"
],
projectDir: nativeDir
});
else if (api === "orpc") await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server"
],
projectDir: nativeDir
});
}
}
const reactBasedFrontends = [
"react-router",
"tanstack-router",
"tanstack-start",
"next",
"native-nativewind",
"native-unistyles"
];
const needsSolidQuery = frontend.includes("solid");
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
if (needsReactQuery && !isConvex) {
const reactQueryDeps = ["@tanstack/react-query"];
const reactQueryDevDeps = ["@tanstack/react-query-devtools"];
const hasReactWeb$1 = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
if (hasReactWeb$1 && webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) try {
await addPackageDependency({
dependencies: reactQueryDeps,
devDependencies: reactQueryDevDeps,
projectDir: webDir
});
} catch (_error) {}
}
if (hasNative && nativeDirExists) {
const nativePkgJsonPath = path.join(nativeDir, "package.json");
if (await fs.pathExists(nativePkgJsonPath)) try {
await addPackageDependency({
dependencies: reactQueryDeps,
projectDir: nativeDir
});
} catch (_error) {}
}
}
if (needsSolidQuery && !isConvex) {
const solidQueryDeps = ["@tanstack/solid-query"];
const solidQueryDevDeps = ["@tanstack/solid-query-devtools"];
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) try {
await addPackageDependency({
dependencies: solidQueryDeps,
devDependencies: solidQueryDevDeps,
projectDir: webDir
});
} catch (_error) {}
}
}
if (isConvex) {
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) try {
const webDepsToAdd = ["convex"];
if (frontend.includes("tanstack-start")) webDepsToAdd.push("@convex-dev/react-query");
if (hasSvelteWeb) webDepsToAdd.push("convex-svelte");
await addPackageDependency({
dependencies: webDepsToAdd,
projectDir: webDir
});
} catch (_error) {}
}
if (nativeDirExists) {
const nativePkgJsonPath = path.join(nativeDir, "package.json");
if (await fs.pathExists(nativePkgJsonPath)) try {
await addPackageDependency({
dependencies: ["convex"],
projectDir: nativeDir
});
} catch (_error) {}
}
const backendPackageName = `@${projectName}/backend`;
const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
const addWorkspaceDepManually = async (pkgJsonPath, depName, depVersion) => {
try {
const pkgJson = await fs.readJson(pkgJsonPath);
if (!pkgJson.dependencies) pkgJson.dependencies = {};
if (pkgJson.dependencies[depName] !== depVersion) {
pkgJson.dependencies[depName] = depVersion;
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
}
} catch (_error) {}
};
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) await addWorkspaceDepManually(webPkgJsonPath, backendPackageName, backendWorkspaceVersion);
}
if (nativeDirExists) {
const nativePkgJsonPath = path.join(nativeDir, "package.json");
if (await fs.pathExists(nativePkgJsonPath)) await addWorkspaceDepManually(nativePkgJsonPath, backendPackageName, backendWorkspaceVersion);
}
}
}
//#endregion
//#region src/helpers/setup/auth-setup.ts
async function setupAuth(config) {
const { auth, frontend, backend, projectDir } = config;
if (backend === "convex" || !auth) return;
const serverDir = path.join(projectDir, "apps/server");
const clientDir = path.join(projectDir, "apps/web");
const nativeDir = path.join(projectDir, "apps/native");
const clientDirExists = await fs.pathExists(clientDir);
const nativeDirExists = await fs.pathExists(nativeDir);
const serverDirExists = await fs.pathExists(serverDir);
try {
if (serverDirExists) await addPackageDependency({
dependencies: ["better-auth"],
projectDir: serverDir
});
const hasWebFrontend = frontend.some((f) => [
"react-router",
"tanstack-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid"
].includes(f));
if (hasWebFrontend && clientDirExists) await addPackageDependency({
dependencies: ["better-auth"],
projectDir: clientDir
});
if ((frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) && nativeDirExists) {
await addPackageDependency({
dependencies: ["better-auth", "@better-auth/expo"],
projectDir: nativeDir
});
if (serverDirExists) await addPackageDependency({
dependencies: ["@better-auth/expo"],
projectDir: serverDir
});
}
} catch (error) {
consola.error(pc.red("Failed to configure authentication dependencies"));
if (error instanceof Error) consola.error(pc.red(error.message));
}
}
function generateAuthSecret(length = 32) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
return result;
}
//#endregion
//#region src/helpers/setup/backend-setup.ts
async function setupBackendDependencies(config) {
const { backend, runtime, api, projectDir } = config;
if (backend === "convex") return;
const framework = backend;
const serverDir = path.join(projectDir, "apps/server");
const dependencies = [];
const devDependencies = [];
if (framework === "hono") {
dependencies.push("hono");
if (api === "trpc") dependencies.push("@hono/trpc-server");
if (runtime === "node") {
dependencies.push("@hono/node-server");
devDependencies.push("tsx", "@types/node");
}
} else if (framework === "elysia") {
dependencies.push("elysia", "@elysiajs/cors");
if (api === "trpc") dependencies.push("@elysiajs/trpc");
if (runtime === "node") {
dependencies.push("@elysiajs/node");
devDependencies.push("tsx", "@types/node");
}
} else if (framework === "express") {
dependencies.push("express", "cors");
devDependencies.push("@types/express", "@types/cors");
if (runtime === "node") devDependencies.push("tsx", "@types/node");
} else if (framework === "fastify") {
dependencies.push("fastify", "@fastify/cors");
if (runtime === "node") devDependencies.push("tsx", "@types/node");
}
if (runtime === "bun") devDependencies.push("@types/bun");
if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
dependencies,
devDependencies,
projectDir: serverDir
});
}
//#endregion
//#region src/utils/command-exists.ts
async function commandExists(command) {
try {
const isWindows = process.platform === "win32";
if (isWindows) {
const result$1 = await execa("where", [command]);
return result$1.exitCode === 0;
}
const result = await execa("which", [command]);
return result.exitCode === 0;
} catch {
return false;
}
}
//#endregion
//#region src/helpers/project-generation/env-setup.ts
async function addEnvVariablesToFile(filePath, variables) {
await fs.ensureDir(path.dirname(filePath));
let envContent = "";
if (await fs.pathExists(filePath)) envContent = await fs.readFile(filePath, "utf8");
let modified = false;
let contentToAdd = "";
const exampleVariables = [];
for (const { key, value, condition } of variables) if (condition) {
const regex = new RegExp(`^${key}=.*$`, "m");
const valueToWrite = value ?? "";
exampleVariables.push(`${key}=`);
if (regex.test(envContent)) {
const existingMatch = envContent.match(regex);
if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
modified = true;
}
} else {
contentToAdd += `${key}=${valueToWrite}\n`;
modified = true;
}
}
if (contentToAdd) {
if (envContent.length > 0 && !envContent.endsWith("\n")) envContent += "\n";
envContent += contentToAdd;
}
if (modified) await fs.writeFile(filePath, envContent.trimEnd());
const exampleFilePath = filePath.replace(/\.env$/, ".env.example");
let exampleEnvContent = "";
if (await fs.pathExists(exampleFilePath)) exampleEnvContent = await fs.readFile(exampleFilePath, "utf8");
let exampleModified = false;
let exampleContentToAdd = "";
for (const exampleVar of exampleVariables) {
const key = exampleVar.split("=")[0];
const regex = new RegExp(`^${key}=.*$`, "m");
if (!regex.test(exampleEnvContent)) {
exampleContentToAdd += `${exampleVar}\n`;
exampleModified = true;
}
}
if (exampleContentToAdd) {
if (exampleEnvContent.length > 0 && !exampleEnvContent.endsWith("\n")) exampleEnvContent += "\n";
exampleEnvContent += exampleContentToAdd;
}
if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
}
async function setupEnvironmentVariables(config) {
const { backend, frontend, database, auth, examples, dbSetup, projectDir } = config;
const hasReactRouter = frontend.includes("react-router");
const hasTanStackRouter = frontend.includes("tanstack-router");
const hasTanStackStart = frontend.includes("tanstack-start");
const hasNextJs = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");
const hasSvelte = frontend.includes("svelte");
const hasSolid = frontend.includes("solid");
const hasWebFrontend = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
if (hasWebFrontend) {
const clientDir = path.join(projectDir, "apps/web");
if (await fs.pathExists(clientDir)) {
let envVarName = "VITE_SERVER_URL";
let serverUrl = "http://localhost:3000";
if (hasNextJs) envVarName = "NEXT_PUBLIC_SERVER_URL";
else if (hasNuxt) envVarName = "NUXT_PUBLIC_SERVER_URL";
else if (hasSvelte) envVarName = "PUBLIC_SERVER_URL";
if (backend === "convex") {
if (hasNextJs) envVarName = "NEXT_PUBLIC_CONVEX_URL";
else if (hasNuxt) envVarName = "NUXT_PUBLIC_CONVEX_URL";
else if (hasSvelte) envVarName = "PUBLIC_CONVEX_URL";
else envVarName = "VITE_CONVEX_URL";
serverUrl = "https://<YOUR_CONVEX_URL>";
}
const clientVars = [{
key: envVarName,
value: serverUrl,
condition: true
}];
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
}
}
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) {
const nativeDir = path.join(projectDir, "apps/native");
if (await fs.pathExists(nativeDir)) {
let envVarName = "EXPO_PUBLIC_SERVER_URL";
let serverUrl = "http://localhost:3000";
if (backend === "convex") {
envVarName = "EXPO_PUBLIC_CONVEX_URL";
serverUrl = "https://<YOUR_CONVEX_URL>";
}
const nativeVars = [{
key: envVarName,
value: serverUrl,
condition: true
}];
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
}
}
if (backend === "convex") return;
const serverDir = path.join(projectDir, "apps/server");
if (!await fs.pathExists(serverDir)) return;
const envPath = path.join(serverDir, ".env");
let corsOrigin = "http://localhost:3001";
if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
let databaseUrl = null;
const specializedSetup = dbSetup === "turso" || dbSetup === "prisma-postgres" || dbSetup === "mongodb-atlas" || dbSetup === "neon" || dbSetup === "supabase";
if (database !== "none" && !specializedSetup) switch (database) {
case "postgres":
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
break;
case "mysql":
databaseUrl = "mysql://root:password@localhost:3306/mydb";
break;
case "mongodb":
databaseUrl = "mongodb://localhost:27017/mydatabase";
break;
case "sqlite":
if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
else databaseUrl = "file:./local.db";
break;
}
const serverVars = [
{
key: "CORS_ORIGIN",
value: corsOrigin,
condition: true
},
{
key: "BETTER_AUTH_SECRET",
value: generateAuthSecret(),
condition: !!auth
},
{
key: "BETTER_AUTH_URL",
value: "http://localhost:3000",
condition: !!auth
},
{
key: "DATABASE_URL",
value: databaseUrl,
condition: database !== "none" && !specializedSetup
},
{
key: "GOOGLE_GENERATIVE_AI_API_KEY",
value: "",
condition: examples?.includes("ai") || false
}
];
await addEnvVariablesToFile(envPath, serverVars);
if (config.runtime === "workers") {
const devVarsPath = path.join(serverDir, ".dev.vars");
try {
await fs.copy(envPath, devVarsPath);
} catch (_err) {}
}
}
//#endregion
//#region src/helpers/database-providers/mongodb-atlas-setup.ts
async function checkAtlasCLI() {
const s = spinner();
s.start("Checking for MongoDB Atlas CLI...");
try {
const exists = await commandExists("atlas");
s.stop(exists ? "MongoDB Atlas CLI found" : pc.yellow("MongoDB Atlas CLI not found"));
return exists;
} catch (_error) {
s.stop(pc.red("Error checking MongoDB Atlas CLI"));
return false;
}
}
async function initMongoDBAtlas(serverDir) {
try {
const hasAtlas = await checkAtlasCLI();
if (!hasAtlas) {
consola.error(pc.red("MongoDB Atlas CLI not found."));
log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
return null;
}
log.info(pc.blue("Running MongoDB Atlas setup..."));
await execa("atlas", ["deployments", "setup"], {
cwd: serverDir,
stdio: "inherit"
});
log.info(pc.green("MongoDB Atlas deployment ready"));
const connectionString = await text({
message: "Enter your MongoDB connection string:",
placeholder: "mongodb+srv://username:password@cluster.mongodb.net/database",
validate(value) {
if (!value) return "Please enter a connection string";
if (!value.startsWith("mongodb")) return "URL should start with mongodb:// or mongodb+srv://";
}
});
if (isCancel(connectionString)) {
cancel("MongoDB setup cancelled");
return null;
}
return { connectionString };
} catch (error) {
if (error instanceof Error) consola.error(pc.red(error.message));
return null;
}
}
async function writeEnvFile$3(projectDir, config) {
try {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [{
key: "DATABASE_URL",
value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
condition: true
}];
await addEnvVariablesToFile(envPath, variables);
} catch (_error) {
consola.error("Failed to update environment configuration");
}
}
function displayManualSetupInstructions$3() {
log.info(`
${pc.green("MongoDB Atlas Manual Setup Instructions:")}
1. Install Atlas CLI:
${pc.blue("https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/")}
2. Run the following command and follow the prompts:
${pc.blue("atlas deployments setup")}
3. Get your connection string from the Atlas dashboard:
Format: ${pc.dim("mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME")}
4. Add the connection string to your .env file:
${pc.dim("DATABASE_URL=\"your_connection_string\"")}
`);
}
async function setupMongoDBAtlas(config) {
const { projectDir } = config;
const mainSpinner = spinner();
mainSpinner.start("Setting up MongoDB Atlas...");
const serverDir = path.join(projectDir, "apps/server");
try {
await fs.ensureDir(serverDir);
mainSpinner.stop("MongoDB Atlas setup ready");
const config$1 = await initMongoDBAtlas(serverDir);
if (config$1) {
await writeEnvFile$3(projectDir, config$1);
log.success(pc.green("MongoDB Atlas setup complete! Connection saved to .env file."));
} else {
log.warn(pc.yellow("Falling back to local MongoDB configuration"));
await writeEnvFile$3(projectDir);
displayManualSetupInstructions$3();
}
} catch (error) {
mainSpinner.stop(pc.red("MongoDB Atlas setup failed"));
consola.error(pc.red(`Error during MongoDB Atlas setup: ${error instanceof Error ? error.message : String(error)}`));
try {
await writeEnvFile$3(projectDir);
displayManualSetupInstructions$3();
} catch {}
}
}
//#endregion
//#region src/helpers/database-providers/neon-setup.ts
const NEON_REGIONS = [
{
label: "AWS US East (N. Virginia)",
value: "aws-us-east-1"
},
{
label: "AWS US East (Ohio)",
value: "aws-us-east-2"
},
{
label: "AWS US West (Oregon)",
value: "aws-us-west-2"
},
{
label: "AWS Europe (Frankfurt)",
value: "aws-eu-central-1"
},
{
label: "AWS Asia Pacific (Singapore)",
value: "aws-ap-southeast-1"
},
{
label: "AWS Asia Pacific (Sydney)",
value: "aws-ap-southeast-2"
},
{
label: "Azure East US 2 region (Virginia)",
value: "azure-eastus2"
}
];
async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
const s = spinner();
try {
const fullCommand = getPackageExecutionCommand(packageManager, commandArgsString);
if (spinnerText) s.start(spinnerText);
const result = await execa(fullCommand, { shell: true });
if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
return result;
} catch (error) {
if (s) s.stop(pc.red(`Failed: ${spinnerText || "Command execution"}`));
throw error;
}
}
async function createNeonProject(projectName, regionId, packageManager) {
try {
const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
const response = JSON.parse(stdout);
if (response.project && response.connection_uris && response.connection_uris.length > 0) {
const projectId = response.project.id;
const connectionUri = response.connection_uris[0].connection_uri;
const params = response.connection_uris[0].connection_parameters;
return {
connectionString: connectionUri,
projectId,
dbName: params.database,
roleName: params.role
};
}
consola$1.error(pc.red("Failed to extract connection information from response"));
return null;
} catch (_error) {
consola$1.error(pc.red("Failed to create Neon project"));
}
}
async function writeEnvFile$2(projectDir, config) {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [{
key: "DATABASE_URL",
value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
condition: true
}];
await addEnvVariablesToFile(envPath, variables);
return true;
}
async function setupWithNeonDb(projectDir, packageManager) {
try {
const s = spinner();
s.start("Creating Neon database using neondb...");
const serverDir = path.join(projectDir, "apps/server");
await fs.ensureDir(serverDir);
const packageCmd = getPackageExecutionCommand(packageManager, "neondb --yes");
await execa(packageCmd, {
shell: true,
cwd: serverDir
});
s.stop(pc.green("Neon database created successfully!"));
return true;
} catch (error) {
consola$1.error(pc.red("Failed to create database with neondb"));
throw error;
}
}
function displayManualSetupInstructions$2() {
log.info(`Manual Neon PostgreSQL Setup Instructions:
1. Visit https://neon.tech and create an account
2. Create a new project from the dashboard
3. Get your connection string
4. Add the database URL to the .env file in apps/server/.env
DATABASE_URL="your_connection_string"`);
}
async function setupNeonPostgres(config) {
const { packageManager, projectDir } = config;
try {
const setupMethod = await select({
message: "Choose your Neon setup method:",
options: [{
label: "Quick setup with neondb",
value: "neondb",
hint: "fastest, no auth required"
}, {
label: "Custom setup with neonctl",
value: "neonctl",
hint: "More control - choose project name and region"
}],
initialValue: "neondb"
});
if (isCancel(setupMethod)) {
cancel(pc.red("Operation cancelled"));
process.exit(0);
}
if (setupMethod === "neondb") await setupWithNeonDb(projectDir, packageManager);
else {
const suggestedProjectName = path.basename(projectDir);
const projectName = await text({
message: "Enter a name for your Neon project:",
defaultValue: suggestedProjectName,
initialValue: suggestedProjectName
});
const regionId = await select({
message: "Select a region for your Neon project:",
options: NEON_REGIONS,
initialValue: NEON_REGIONS[0].value
});
if (isCancel(projectName) || isCancel(regionId)) {
cancel(pc.red("Operation cancelled"));
process.exit(0);
}
const neonConfig = await createNeonProject(projectName, regionId, packageManager);
if (!neonConfig) throw new Error("Failed to create project - couldn't get connection information");
const finalSpinner = spinner();
finalSpinner.start("Configuring database connection");
await fs.ensureDir(path.join(projectDir, "apps/server"));
await writeEnvFile$2(projectDir, neonConfig);
finalSpinner.stop("Neon database configured!");
}
} catch (error) {
if (error instanceof Error) consola$1.error(pc.red(error.message));
await writeEnvFile$2(projectDir);
displayManualSetupInstructions$2();
}
}
//#endregion
//#region src/helpers/database-providers/prisma-postgres-setup.ts
async function initPrismaDatabase(serverDir, packageManager) {
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:");
const prismaInitCommand = getPackageExecutionCommand(packageManager, "prisma init --db");
await execa(prismaInitCommand, {
cwd: serverDir,
stdio: "inherit",
shell: true
});
log.info(pc.yellow("Please copy the Prisma Postgres URL from the output above.\nIt looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=..."));
const databaseUrl = await password({
message: "Paste your Prisma Postgres database URL:",
validate(value) {
if (!value) return "Please enter a database URL";
if (!value.startsWith("prisma+postgres://")) return "URL should start with prisma+postgres://";
}
});
if (isCancel(databaseUrl)) {
cancel("Database setup cancelled");
return null;
}
return { databaseUrl };
} catch (error) {
s.stop(pc.red("Prisma PostgreSQL initialization failed"));
if (error instanceof Error) consola$1.error(error.message);
return null;
}
}
async function writeEnvFile$1(projectDir, config) {
try {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [{
key: "DATABASE_URL",
value: config?.databaseUrl ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
condition: true
}];
await addEnvVariablesToFile(envPath, variables);
} catch (_error) {
consola$1.error("Failed to update environment configuration");
}
}
function displayManualSetupInstructions$1() {
log.info(`Manual Prisma PostgreSQL Setup Instructions:
1. Visit https://console.prisma.io and create an account
2. Create a new PostgreSQL database from the dashboard
3. Get your database URL
4. Add the database URL to the .env file in apps/server/.env
DATABASE_URL="your_database_url"`);
}
async function addPrismaAccelerateExtension(serverDir) {
try {
await addPackageDependency({
dependencies: ["@prisma/extension-accelerate"],
projectDir: serverDir
});
const prismaIndexPath = path.join(serverDir, "prisma/index.ts");
const prismaIndexContent = `
import { PrismaClient } from "./generated/client";
import { withAccelerate } from "@prisma/extension-accelerate";
const prisma = new PrismaClient().$extends(withAccelerate());
export default prisma;
`;
await fs.writeFile(prismaIndexPath, prismaIndexContent.trim());
const dbFilePath = path.join(serverDir, "src/db/index.ts");
if (await fs.pathExists(dbFilePath)) {
let dbFileContent = await fs.readFile(dbFilePath, "utf8");
if (!dbFileContent.includes("@prisma/extension-accelerate")) {
dbFileContent = `import { withAccelerate } from "@prisma/extension-accelerate";\n${dbFileContent}`;
dbFileContent = dbFileContent.replace("export const db = new PrismaClient();", "export const db = new PrismaClient().$extends(withAccelerate());");
await fs.writeFile(dbFilePath, dbFileContent);
}
}
return true;
} catch (_error) {
log.warn(pc.yellow("Could not add Prisma Accelerate extension automatically"));
return false;
}
}
async function setupPrismaPostgres(config) {
const { packageManager, projectDir } = 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 config$1 = await initPrismaDatabase(serverDir, packageManager);
if (config$1) {
await writeEnvFile$1(projectDir, config$1);
await addPrismaAccelerateExtension(serverDir);
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...");
await writeEnvFile$1(projectDir);
fallbackSpinner.stop("Fallback configuration ready");
displayManualSetupInstructions$1();
}
} catch (error) {
s.stop(pc.red("Prisma PostgreSQL setup failed"));
consola$1.error(pc.red(`Error during Prisma PostgreSQL setup: ${error instanceof Error ? error.message : String(error)}`));
try {
await writeEnvFile$1(projectDir);
displayManualSetupInstructions$1();
} catch {}
log.info("Setup completed with manual configuration required.");
}
}
//#endregion
//#region src/helpers/database-providers/supabase-setup.ts
async function writeSupabaseEnvFile(projectDir, databaseUrl) {
try {
const envPath = path.join(projectDir, "apps/server", ".env");
const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
const variables = [{
key: "DATABASE_URL",
value: dbUrlToUse,
condition: true
}, {
key: "DIRECT_URL",
value: dbUrlToUse,
condition: true
}];
await addEnvVariablesToFile(envPath, variables);
return true;
} catch (error) {
consola$1.error(pc.red("Failed to update .env file for Supabase."));
if (error instanceof Error) consola$1.error(error.message);
return false;
}
}
function extractDbUrl(output) {
const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
const url = dbUrlMatch?.[1];
if (url) return url;
return null;
}
async function initializeSupabase(serverDir, packageManager) {
log.info("Initializing Supabase project...");
try {
const supabaseInitCommand = getPackageExecutionCommand(packageManager, "supabase init");
await execa(supabaseInitCommand, {
cwd: serverDir,
stdio: "inherit",
shell: true
});
log.success("Supabase project initialized");
return true;
} catch (error) {
consola$1.error(pc.red("Failed to initialize Supabase project."));
if (error instanceof Error) consola$1.error(error.message);
else consola$1.error(String(error));
if (error instanceof Error && error.message.includes("ENOENT")) {
log.error(pc.red("Supabase CLI not found. Please install it globally or ensure it's in your PATH."));
log.info("You can install it using: npm install -g supabase");
}
return false;
}
}
async function startSupabase(serverDir, packageManager) {
log.info("Starting Supabase services (this may take a moment)...");
const supabaseStartCommand = getPackageExecutionCommand(packageManager, "supabase start");
try {
const subprocess = execa(supabaseStartCommand, {
cwd: serverDir,
shell: true
});
let stdoutData = "";
if (subprocess.stdout) subprocess.stdout.on("data", (data) => {
const text$1 = data.toString();
process.stdout.write(text$1);
stdoutData += text$1;
});
if (subprocess.stderr) subprocess.stderr.pipe(process.stderr);
await subprocess;
await new Promise((resolve) => setTimeout(resolve, 100));
return stdoutData;
} catch (error) {
consola$1.error(pc.red("Failed to start Supabase services."));
const execaError = error;
if (execaError?.message) {
consola$1.error(`Error details: ${execaError.message}`);
if (execaError.message.includes("Docker is not running")) log.error(pc.red("Docker is not running. Please start Docker and try again."));
} else consola$1.error(String(error));
return null;
}
}
function displayManualSupabaseInstructions(output) {
log.info(`"Manual Supabase Setup Instructions:"
1. Ensure Docker is installed and running.
2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
3. Run \`supabase init\` in your project's \`apps/server\` directory.
4. Run \`supabase start\` in your project's \`apps/server\` directory.
5. Copy the 'DB URL' from the output.${output ? `
${pc.bold("Relevant output from `supabase start`:")}
${pc.dim(output)}` : ""}
6. Add the DB URL to the .env file in \`apps/server/.env\` as \`DATABASE_URL\`:
${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
}
async function setupSupabase(config) {
const { projectDir, packageManager } = config;
const serverDir = path.join(projectDir, "apps", "server");
try {
await fs.ensureDir(serverDir);
const initialized = await initializeSupabase(serverDir, packageManager);
if (!initialized) {
displayManualSupabaseInstructions();
return;
}
const supabaseOutput = await startSupabase(serverDir, packageManager);
if (!supabaseOutput) {
displayManualSupabaseInstructions();
return;
}
const dbUrl = extractDbUrl(supabaseOutput);
if (dbUrl) {
const envUpdated = await writeSupabaseEnvFile(projectDir, dbUrl);
if (envUpdated) log.success(pc.green("Supabase local development setup ready!"));
else {
log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
displayManualSupabaseInstructions(supabaseOutput);
}
} else {
log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
displayManualSupabaseInstructions(supabaseOutput);
}
} catch (error) {
if (error instanceof Error) consola$1.error(pc.red(`Error during Supabase setup: ${error.message}`));
else consola$1.error(pc.red(`An unknown error occurred during Supabase setup: ${String(error)}`));
displayManualSupabaseInstructions();
}
}
//#endregion
//#region src/helpers/database-providers/turso-setup.ts
async function isTursoInstalled() {
return commandExists("turso");
}
async function isTursoLoggedIn() {
try {
const output = await $`turso auth whoami`;
return !output.stdout.includes("You are not logged in");
} catch {
return false;
}
}
async function loginToTurso() {
const s = spinner();
try {
s.start("Logging in to Turso...");
await $`turso auth login`;
s.stop("Logged into Turso");
return true;
} catch (_error) {
s.stop(pc.red("Failed to log in to Turso"));
}
}
async function installTursoCLI(isMac) {
const s = spinner();
try {
s.start("Installing Turso CLI...");
if (isMac) await $`brew install tursodatabase/tap/turso`;
else {
const { stdout: installScript } = await $`curl -sSfL https://get.tur.so/install.sh`;
await $`bash -c '${installScript}'`;
}
s.stop("Turso CLI installed");
return true;
} catch (error) {
if (error instanceof Error && error.message.includes("User force closed")) {
s.stop("Turso CLI installation cancelled");
log.warn(pc.yellow("Turso CLI installation cancelled by user"));
throw new Error("Installation cancelled");
}
s.stop(pc.red("Failed to install Turso CLI"));
}
}
async function getTursoGroups() {
const s = spinner();
try {
s.start("Fetching Turso groups...");
const { stdout } = await $`turso group list`;
const lines = stdout.trim().split("\n");
if (lines.length <= 1) {
s.stop("No Turso groups found");
return [];
}
const groups = lines.slice(1).map((line) => {
const [name, locations, version, status] = line.trim().split(/\s{2,}/);
return {
name,
locations,
version,
status
};
});
s.stop(`Found ${groups.length} Turso groups`);
return groups;
} catch (error) {
s.stop(pc.red("Error fetching Turso groups"));
console.error("Error fetching Turso groups:", error);
return [];
}
}
async function selectTursoGroup() {
const groups = await getTursoGroups();
if (groups.length === 0) return null;
if (groups.length === 1) {
log.info(`Using the only available group: ${pc.blue(groups[0].name)}`);
return groups[0].name;
}
const groupOptions = groups.map((group$1) => ({
value: group$1.name,
label: `${group$1.name} (${group$1.locations})`
}));
const selectedGroup = await select({
message: "Select a Turso database group:",
options: groupOptions
});
if (isCancel(selectedGroup)) {
cancel(pc.red("Operation cancelled"));
process.exit(0);
}
return selectedGroup;
}
async function createTursoDatabase(dbName, groupName) {
const s = spinner();
try {
s.start(`Creating Turso database "${dbName}"${groupName ? ` in group "${groupName}"` : ""}...`);
if (groupName) await $`turso db create ${dbName} --group ${groupName}`;
else await $`turso db create ${dbName}`;
s.stop(`Turso database "${dbName}" created`);
} catch (error) {
s.stop(pc.red(`Failed to create database "${dbName}"`));
if (error instanceof Error && error.message.includes("already exists")) throw new Error("DATABASE_EXISTS");
}
s.start("Retrieving database connection details...");
try {
const { stdout: dbUrl } = await $`turso db show ${dbName} --url`;
const { stdout: authToken } = await $`turso db tokens create ${dbName}`;
s.stop("Database connection details retrieved");
return {
dbUrl: dbUrl.trim(),
authToken: authToken.trim()
};
} catch (_error) {
s.stop(pc.red("Failed to retrieve database connection details"));
}
}
async function writeEnvFile(projectDir, config) {
const envPath = path.join(projectDir, "apps/server", ".env");
const variables = [{
key: "DATABASE_URL",
value: config?.dbUrl ?? "",
condition: true
}, {
key: "DATABASE_AUTH_TOKEN",
value: config?.authToken ?? "",
condition: true
}];
await addEnvVariablesToFile(envPath, variables);
}
function displayManualSetupInstructions() {
log.info(`Manual Turso Setup Instructions:
1. Visit https://turso.tech and create an account
2. Create a new database from the dashboard
3. Get your database URL and authentication token
4. Add these credentials to the .env file in apps/server/.env
DATABASE_URL=your_database_url
DATABASE_AUTH_TOKEN=your_auth_token`);
}
async function setupTurso(config) {
const { orm, projectDir } = config;
const _isDrizzle = orm === "drizzle";
const setupSpinner = spinner();
setupSpinner.start("Checking Turso CLI availability...");
try {
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"));
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
await writeEnvFile(projectDir);
displayManualSetupInstructions();
return;
}
setupSpinner.stop("Turso CLI availability checked");
const isCliInstalled = await isTursoInstalled();
if (!isCliInstalled) {
const shouldInstall = await confirm({
message: "Would you like to install Turso CLI?",
initialValue: true
});
if (isCancel(shouldInstall)) {
cancel(pc.red("Operation cancelled"));
pro