feat: Auto-generate .env.example files with empty values

This commit is contained in:
Aman Varshney
2025-05-24 13:04:30 +05:30
parent 0480bb7c82
commit 1cc9d81944
8 changed files with 114 additions and 96 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": minor
---
Auto-generate .env.example files with empty values

View File

@@ -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<void> {
@@ -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";

View File

@@ -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)
}`,
),
);

View File

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

View File

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

View File

@@ -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<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());
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."));

View File

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

View File

@@ -28,6 +28,7 @@ node_modules/
# env
.env*
.env.production
!.env.example
.dev.vars
# logs