mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add supabase database setup (#254)
This commit is contained in:
@@ -6,6 +6,7 @@ import pc from "picocolors";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
import { setupMongoDBAtlas } from "./mongodb-atlas-setup";
|
||||
import { setupPrismaPostgres } from "./prisma-postgres-setup";
|
||||
import { setupSupabase } from "./supabase-setup";
|
||||
import { setupTurso } from "./turso-setup";
|
||||
|
||||
import { setupNeonPostgres } from "./neon-setup";
|
||||
@@ -13,7 +14,7 @@ import { setupNeonPostgres } from "./neon-setup";
|
||||
import type { ProjectConfig } from "../types";
|
||||
|
||||
export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
||||
const { projectName, database, orm, dbSetup, backend, projectDir } = config;
|
||||
const { database, orm, dbSetup, backend, projectDir } = config;
|
||||
|
||||
if (backend === "convex" || database === "none") {
|
||||
if (backend !== "convex") {
|
||||
@@ -75,6 +76,8 @@ export async function setupDatabase(config: ProjectConfig): Promise<void> {
|
||||
await setupPrismaPostgres(config);
|
||||
} else if (dbSetup === "neon") {
|
||||
await setupNeonPostgres(config);
|
||||
} else if (dbSetup === "supabase") {
|
||||
await setupSupabase(config);
|
||||
}
|
||||
} else if (database === "mongodb" && dbSetup === "mongodb-atlas") {
|
||||
await setupMongoDBAtlas(config);
|
||||
|
||||
@@ -163,7 +163,8 @@ export async function setupEnvironmentVariables(
|
||||
dbSetup === "turso" ||
|
||||
dbSetup === "prisma-postgres" ||
|
||||
dbSetup === "mongodb-atlas" ||
|
||||
dbSetup === "neon";
|
||||
dbSetup === "neon" ||
|
||||
dbSetup === "supabase";
|
||||
|
||||
if (database !== "none" && !specializedSetup) {
|
||||
switch (database) {
|
||||
|
||||
228
apps/cli/src/helpers/supabase-setup.ts
Normal file
228
apps/cli/src/helpers/supabase-setup.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import path from "node:path";
|
||||
import { log } from "@clack/prompts";
|
||||
import { consola } from "consola";
|
||||
import { type ExecaError, execa } from "execa";
|
||||
import fs from "fs-extra";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectConfig, ProjectPackageManager } from "../types";
|
||||
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
||||
|
||||
async function writeSupabaseEnvFile(
|
||||
projectDir: string,
|
||||
databaseUrl: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||
await fs.ensureDir(path.dirname(envPath));
|
||||
|
||||
let envContent = "";
|
||||
if (await fs.pathExists(envPath)) {
|
||||
envContent = await fs.readFile(envPath, "utf8");
|
||||
}
|
||||
|
||||
const dbUrlToUse =
|
||||
databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
||||
|
||||
const databaseUrlLine = `DATABASE_URL="${dbUrlToUse}"`;
|
||||
const directUrlLine = `DIRECT_URL="${dbUrlToUse}"`;
|
||||
|
||||
if (!envContent.includes("DATABASE_URL=")) {
|
||||
envContent += `\n${databaseUrlLine}`;
|
||||
} else {
|
||||
envContent = envContent.replace(
|
||||
/DATABASE_URL=.*(\r?\n|$)/,
|
||||
`${databaseUrlLine}$1`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!envContent.includes("DIRECT_URL=")) {
|
||||
envContent += `\n${directUrlLine}`;
|
||||
} else {
|
||||
envContent = envContent.replace(
|
||||
/DIRECT_URL=.*(\r?\n|$)/,
|
||||
`${directUrlLine}$1`,
|
||||
);
|
||||
}
|
||||
|
||||
await fs.writeFile(envPath, envContent.trim());
|
||||
return true;
|
||||
} catch (error) {
|
||||
consola.error(pc.red("Failed to update .env file for Supabase."));
|
||||
if (error instanceof Error) {
|
||||
consola.error(error.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function extractDbUrl(output: string): string | null {
|
||||
const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
|
||||
const url = dbUrlMatch?.[1];
|
||||
if (url) {
|
||||
return url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function initializeSupabase(
|
||||
serverDir: string,
|
||||
packageManager: ProjectPackageManager,
|
||||
): Promise<boolean> {
|
||||
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 successfully.");
|
||||
return true;
|
||||
} catch (error) {
|
||||
consola.error(pc.red("Failed to initialize Supabase project."));
|
||||
if (error instanceof Error) {
|
||||
consola.error(error.message);
|
||||
} else {
|
||||
consola.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: string,
|
||||
packageManager: ProjectPackageManager,
|
||||
): Promise<string | null> {
|
||||
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 = data.toString();
|
||||
process.stdout.write(text);
|
||||
stdoutData += text;
|
||||
});
|
||||
}
|
||||
|
||||
if (subprocess.stderr) {
|
||||
subprocess.stderr.pipe(process.stderr);
|
||||
}
|
||||
|
||||
await subprocess;
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
return stdoutData;
|
||||
} catch (error) {
|
||||
consola.error(pc.red("Failed to start Supabase services."));
|
||||
const execaError = error as ExecaError;
|
||||
if (execaError?.message) {
|
||||
consola.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.error(String(error));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function displayManualSupabaseInstructions(output?: string | null) {
|
||||
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"')}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function setupSupabase(config: ProjectConfig) {
|
||||
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 complete!"));
|
||||
} 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.error(pc.red(`Error during Supabase setup: ${error.message}`));
|
||||
} else {
|
||||
consola.error(
|
||||
pc.red(
|
||||
`An unknown error occurred during Supabase setup: ${String(error)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
displayManualSupabaseInstructions();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { DEFAULT_CONFIG } from "./constants";
|
||||
import { createProject } from "./helpers/create-project";
|
||||
import { setupDatabase } from "./helpers/db-setup";
|
||||
import { gatherConfig } from "./prompts/config-prompts";
|
||||
import { getProjectName } from "./prompts/project-name";
|
||||
import type {
|
||||
@@ -128,7 +129,14 @@ async function main() {
|
||||
.option("db-setup", {
|
||||
type: "string",
|
||||
describe: "Database setup",
|
||||
choices: ["turso", "neon", "prisma-postgres", "mongodb-atlas", "none"],
|
||||
choices: [
|
||||
"turso",
|
||||
"neon",
|
||||
"prisma-postgres",
|
||||
"mongodb-atlas",
|
||||
"supabase",
|
||||
"none",
|
||||
],
|
||||
})
|
||||
.option("backend", {
|
||||
type: "string",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cancel, isCancel, log, select } from "@clack/prompts";
|
||||
import { cancel, isCancel, select } from "@clack/prompts";
|
||||
import pc from "picocolors";
|
||||
import type { ProjectBackend, ProjectDBSetup, ProjectOrm } from "../types";
|
||||
|
||||
@@ -41,6 +41,11 @@ export async function getDBSetupChoice(
|
||||
label: "Neon Postgres",
|
||||
hint: "Serverless Postgres with branching capability",
|
||||
},
|
||||
{
|
||||
value: "supabase" as const,
|
||||
label: "Supabase",
|
||||
hint: "Local Supabase stack (requires Docker)",
|
||||
},
|
||||
...(orm === "prisma"
|
||||
? [
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@ export type ProjectDBSetup =
|
||||
| "prisma-postgres"
|
||||
| "mongodb-atlas"
|
||||
| "neon"
|
||||
| "supabase"
|
||||
| "none";
|
||||
export type ProjectApi = "trpc" | "orpc" | "none";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user