add supabase database setup (#254)

This commit is contained in:
Aman Varshney
2025-05-13 19:50:36 +05:30
committed by GitHub
parent 745dca1d6a
commit 5c5a4b2293
12 changed files with 308 additions and 7 deletions

View File

@@ -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);

View File

@@ -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) {

View 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();
}
}

View File

@@ -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",

View File

@@ -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"
? [
{

View File

@@ -39,6 +39,7 @@ export type ProjectDBSetup =
| "prisma-postgres"
| "mongodb-atlas"
| "neon"
| "supabase"
| "none";
export type ProjectApi = "trpc" | "orpc" | "none";