mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat: Auto-generate .env.example files with empty values
This commit is contained in:
5
.changeset/calm-hornets-wish.md
Normal file
5
.changeset/calm-hornets-wish.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Auto-generate .env.example files with empty values
|
||||
@@ -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";
|
||||
|
||||
@@ -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)
|
||||
}`,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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."));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -28,6 +28,7 @@ node_modules/
|
||||
# env
|
||||
.env*
|
||||
.env.production
|
||||
!.env.example
|
||||
.dev.vars
|
||||
|
||||
# logs
|
||||
|
||||
Reference in New Issue
Block a user