diff --git a/.changeset/calm-hornets-wish.md b/.changeset/calm-hornets-wish.md new file mode 100644 index 0000000..479e8fa --- /dev/null +++ b/.changeset/calm-hornets-wish.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Auto-generate .env.example files with empty values diff --git a/apps/cli/src/helpers/env-setup.ts b/apps/cli/src/helpers/env-setup.ts index eb741d1..c72d20e 100644 --- a/apps/cli/src/helpers/env-setup.ts +++ b/apps/cli/src/helpers/env-setup.ts @@ -3,13 +3,13 @@ import fs from "fs-extra"; import type { ProjectConfig } from "../types"; import { generateAuthSecret } from "./auth-setup"; -interface EnvVariable { +export interface EnvVariable { key: string; value: string | null | undefined; condition: boolean; } -async function addEnvVariablesToFile( +export async function addEnvVariablesToFile( filePath: string, variables: EnvVariable[], ): Promise { @@ -22,11 +22,13 @@ async function addEnvVariablesToFile( let modified = false; let contentToAdd = ""; + const exampleVariables: string[] = []; 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); @@ -51,6 +53,35 @@ async function addEnvVariablesToFile( 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()); + } } export async function setupEnvironmentVariables( @@ -160,8 +191,7 @@ export async function setupEnvironmentVariables( if (database !== "none" && !specializedSetup) { switch (database) { case "postgres": - databaseUrl = - "postgresql://postgres:postgres@localhost:5432/mydb?schema=public"; + databaseUrl = "postgresql://postgres:password@localhost:5432/postgres"; break; case "mysql": databaseUrl = "mysql://root:password@localhost:3306/mydb"; diff --git a/apps/cli/src/helpers/mongodb-atlas-setup.ts b/apps/cli/src/helpers/mongodb-atlas-setup.ts index 669c0fd..18ac7aa 100644 --- a/apps/cli/src/helpers/mongodb-atlas-setup.ts +++ b/apps/cli/src/helpers/mongodb-atlas-setup.ts @@ -6,6 +6,7 @@ import fs from "fs-extra"; import pc from "picocolors"; import type { ProjectConfig } from "../types"; import { commandExists } from "../utils/command-exists"; +import { type EnvVariable, addEnvVariablesToFile } from "./env-setup"; type MongoDBConfig = { connectionString: string; @@ -85,27 +86,14 @@ async function initMongoDBAtlas( async function writeEnvFile(projectDir: string, config?: MongoDBConfig) { 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 mongoUrlLine = config - ? `DATABASE_URL="${config.connectionString}"` - : `DATABASE_URL="mongodb://localhost:27017/mydb"`; - - if (!envContent.includes("DATABASE_URL=")) { - envContent += `\n${mongoUrlLine}`; - } else { - envContent = envContent.replace( - /DATABASE_URL=.*(\r?\n|$)/, - `${mongoUrlLine}$1`, - ); - } - - await fs.writeFile(envPath, envContent.trim()); + const variables: EnvVariable[] = [ + { + 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"); } @@ -116,13 +104,17 @@ function displayManualSetupInstructions() { ${pc.green("MongoDB Atlas Manual Setup Instructions:")} 1. Install Atlas CLI: - ${pc.blue("https://www.mongodb.com/docs/atlas/cli/stable/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")} + 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"')} @@ -158,7 +150,9 @@ export async function setupMongoDBAtlas(config: ProjectConfig) { mainSpinner.stop(pc.red("MongoDB Atlas setup failed")); consola.error( pc.red( - `Error during MongoDB Atlas setup: ${error instanceof Error ? error.message : String(error)}`, + `Error during MongoDB Atlas setup: ${ + error instanceof Error ? error.message : String(error) + }`, ), ); diff --git a/apps/cli/src/helpers/neon-setup.ts b/apps/cli/src/helpers/neon-setup.ts index 6249830..c65c030 100644 --- a/apps/cli/src/helpers/neon-setup.ts +++ b/apps/cli/src/helpers/neon-setup.ts @@ -6,6 +6,7 @@ import fs from "fs-extra"; import pc from "picocolors"; import type { ProjectPackageManager } from "../types"; import { getPackageExecutionCommand } from "../utils/get-package-execution-command"; +import { type EnvVariable, addEnvVariablesToFile } from "./env-setup"; type NeonConfig = { connectionString: string; @@ -120,12 +121,16 @@ async function createNeonProject( async function writeEnvFile(projectDir: string, config?: NeonConfig) { const envPath = path.join(projectDir, "apps/server", ".env"); - const envContent = config - ? `DATABASE_URL="${config.connectionString}"` - : `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`; - - await fs.ensureDir(path.dirname(envPath)); - await fs.writeFile(envPath, envContent); + const variables: EnvVariable[] = [ + { + key: "DATABASE_URL", + value: + config?.connectionString ?? + "postgresql://postgres:postgres@localhost:5432/mydb?schema=public", + condition: true, + }, + ]; + await addEnvVariablesToFile(envPath, variables); return true; } diff --git a/apps/cli/src/helpers/prisma-postgres-setup.ts b/apps/cli/src/helpers/prisma-postgres-setup.ts index ca64304..ca3a47a 100644 --- a/apps/cli/src/helpers/prisma-postgres-setup.ts +++ b/apps/cli/src/helpers/prisma-postgres-setup.ts @@ -7,6 +7,7 @@ import pc from "picocolors"; import type { ProjectPackageManager } from "../types"; import { addPackageDependency } from "../utils/add-package-deps"; import { getPackageExecutionCommand } from "../utils/get-package-execution-command"; +import { type EnvVariable, addEnvVariablesToFile } from "./env-setup"; type PrismaConfig = { databaseUrl: string; @@ -72,27 +73,16 @@ async function initPrismaDatabase( async function writeEnvFile(projectDir: string, config?: PrismaConfig) { 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 databaseUrlLine = config - ? `DATABASE_URL="${config.databaseUrl}"` - : `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`; - - if (!envContent.includes("DATABASE_URL=")) { - envContent += `\n${databaseUrlLine}`; - } else { - envContent = envContent.replace( - /DATABASE_URL=.*(\r?\n|$)/, - `${databaseUrlLine}$1`, - ); - } - - await fs.writeFile(envPath, envContent.trim()); + const variables: EnvVariable[] = [ + { + key: "DATABASE_URL", + value: + config?.databaseUrl ?? + "postgresql://postgres:postgres@localhost:5432/mydb?schema=public", + condition: true, + }, + ]; + await addEnvVariablesToFile(envPath, variables); } catch (_error) { consola.error("Failed to update environment configuration"); } diff --git a/apps/cli/src/helpers/supabase-setup.ts b/apps/cli/src/helpers/supabase-setup.ts index 042b583..ce60b3c 100644 --- a/apps/cli/src/helpers/supabase-setup.ts +++ b/apps/cli/src/helpers/supabase-setup.ts @@ -6,6 +6,7 @@ import fs from "fs-extra"; import pc from "picocolors"; import type { ProjectConfig, ProjectPackageManager } from "../types"; import { getPackageExecutionCommand } from "../utils/get-package-execution-command"; +import { type EnvVariable, addEnvVariablesToFile } from "./env-setup"; async function writeSupabaseEnvFile( projectDir: string, @@ -13,38 +14,21 @@ async function writeSupabaseEnvFile( ): Promise { 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()); + const variables: EnvVariable[] = [ + { + key: "DATABASE_URL", + value: dbUrlToUse, + condition: true, + }, + { + key: "DIRECT_URL", + value: dbUrlToUse, + condition: true, + }, + ]; + await addEnvVariablesToFile(envPath, variables); return true; } catch (error) { consola.error(pc.red("Failed to update .env file for Supabase.")); diff --git a/apps/cli/src/helpers/turso-setup.ts b/apps/cli/src/helpers/turso-setup.ts index 721cf1a..ac68c6a 100644 --- a/apps/cli/src/helpers/turso-setup.ts +++ b/apps/cli/src/helpers/turso-setup.ts @@ -11,9 +11,9 @@ import { } from "@clack/prompts"; import consola from "consola"; import { $ } from "execa"; -import fs from "fs-extra"; import pc from "picocolors"; import { commandExists } from "../utils/command-exists"; +import { type EnvVariable, addEnvVariablesToFile } from "./env-setup"; type TursoConfig = { dbUrl: string; @@ -138,7 +138,9 @@ async function createTursoDatabase(dbName: string, groupName: string | null) { try { s.start( - `Creating Turso database "${dbName}"${groupName ? ` in group "${groupName}"` : ""}...`, + `Creating Turso database "${dbName}"${ + groupName ? ` in group "${groupName}"` : "" + }...`, ); if (groupName) { @@ -173,14 +175,19 @@ async function createTursoDatabase(dbName: string, groupName: string | null) { async function writeEnvFile(projectDir: string, config?: TursoConfig) { const envPath = path.join(projectDir, "apps/server", ".env"); - const envContent = config - ? `DATABASE_URL="${config.dbUrl}" -DATABASE_AUTH_TOKEN="${config.authToken}"` - : `DATABASE_URL= -DATABASE_AUTH_TOKEN=`; - - await fs.ensureDir(path.dirname(envPath)); - await fs.writeFile(envPath, envContent); + const variables: EnvVariable[] = [ + { + key: "DATABASE_URL", + value: config?.dbUrl ?? "", + condition: true, + }, + { + key: "DATABASE_AUTH_TOKEN", + value: config?.authToken ?? "", + condition: true, + }, + ]; + await addEnvVariablesToFile(envPath, variables); } function displayManualSetupInstructions() { @@ -288,7 +295,9 @@ export async function setupTurso(config: ProjectConfig): Promise { setupSpinner.stop(pc.red("Failed to set up Turso database")); consola.error( pc.red( - `Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`, + `Error during Turso setup: ${ + error instanceof Error ? error.message : String(error) + }`, ), ); await writeEnvFile(projectDir); diff --git a/apps/cli/templates/backend/server/server-base/_gitignore b/apps/cli/templates/backend/server/server-base/_gitignore index 3c49c35..4b9738b 100644 --- a/apps/cli/templates/backend/server/server-base/_gitignore +++ b/apps/cli/templates/backend/server/server-base/_gitignore @@ -28,6 +28,7 @@ node_modules/ # env .env* .env.production +!.env.example .dev.vars # logs