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 type { ProjectConfig } from "../types";
|
||||||
import { generateAuthSecret } from "./auth-setup";
|
import { generateAuthSecret } from "./auth-setup";
|
||||||
|
|
||||||
interface EnvVariable {
|
export interface EnvVariable {
|
||||||
key: string;
|
key: string;
|
||||||
value: string | null | undefined;
|
value: string | null | undefined;
|
||||||
condition: boolean;
|
condition: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addEnvVariablesToFile(
|
export async function addEnvVariablesToFile(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
variables: EnvVariable[],
|
variables: EnvVariable[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -22,11 +22,13 @@ async function addEnvVariablesToFile(
|
|||||||
|
|
||||||
let modified = false;
|
let modified = false;
|
||||||
let contentToAdd = "";
|
let contentToAdd = "";
|
||||||
|
const exampleVariables: string[] = [];
|
||||||
|
|
||||||
for (const { key, value, condition } of variables) {
|
for (const { key, value, condition } of variables) {
|
||||||
if (condition) {
|
if (condition) {
|
||||||
const regex = new RegExp(`^${key}=.*$`, "m");
|
const regex = new RegExp(`^${key}=.*$`, "m");
|
||||||
const valueToWrite = value ?? "";
|
const valueToWrite = value ?? "";
|
||||||
|
exampleVariables.push(`${key}=`);
|
||||||
|
|
||||||
if (regex.test(envContent)) {
|
if (regex.test(envContent)) {
|
||||||
const existingMatch = envContent.match(regex);
|
const existingMatch = envContent.match(regex);
|
||||||
@@ -51,6 +53,35 @@ async function addEnvVariablesToFile(
|
|||||||
if (modified) {
|
if (modified) {
|
||||||
await fs.writeFile(filePath, envContent.trimEnd());
|
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(
|
export async function setupEnvironmentVariables(
|
||||||
@@ -160,8 +191,7 @@ export async function setupEnvironmentVariables(
|
|||||||
if (database !== "none" && !specializedSetup) {
|
if (database !== "none" && !specializedSetup) {
|
||||||
switch (database) {
|
switch (database) {
|
||||||
case "postgres":
|
case "postgres":
|
||||||
databaseUrl =
|
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
|
||||||
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public";
|
|
||||||
break;
|
break;
|
||||||
case "mysql":
|
case "mysql":
|
||||||
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
databaseUrl = "mysql://root:password@localhost:3306/mydb";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import fs from "fs-extra";
|
|||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import type { ProjectConfig } from "../types";
|
import type { ProjectConfig } from "../types";
|
||||||
import { commandExists } from "../utils/command-exists";
|
import { commandExists } from "../utils/command-exists";
|
||||||
|
import { type EnvVariable, addEnvVariablesToFile } from "./env-setup";
|
||||||
|
|
||||||
type MongoDBConfig = {
|
type MongoDBConfig = {
|
||||||
connectionString: string;
|
connectionString: string;
|
||||||
@@ -85,27 +86,14 @@ async function initMongoDBAtlas(
|
|||||||
async function writeEnvFile(projectDir: string, config?: MongoDBConfig) {
|
async function writeEnvFile(projectDir: string, config?: MongoDBConfig) {
|
||||||
try {
|
try {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||||
await fs.ensureDir(path.dirname(envPath));
|
const variables: EnvVariable[] = [
|
||||||
|
{
|
||||||
let envContent = "";
|
key: "DATABASE_URL",
|
||||||
if (await fs.pathExists(envPath)) {
|
value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
|
||||||
envContent = await fs.readFile(envPath, "utf8");
|
condition: true,
|
||||||
}
|
},
|
||||||
|
];
|
||||||
const mongoUrlLine = config
|
await addEnvVariablesToFile(envPath, variables);
|
||||||
? `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());
|
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
consola.error("Failed to update environment configuration");
|
consola.error("Failed to update environment configuration");
|
||||||
}
|
}
|
||||||
@@ -116,13 +104,17 @@ function displayManualSetupInstructions() {
|
|||||||
${pc.green("MongoDB Atlas Manual Setup Instructions:")}
|
${pc.green("MongoDB Atlas Manual Setup Instructions:")}
|
||||||
|
|
||||||
1. Install Atlas CLI:
|
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:
|
2. Run the following command and follow the prompts:
|
||||||
${pc.blue("atlas deployments setup")}
|
${pc.blue("atlas deployments setup")}
|
||||||
|
|
||||||
3. Get your connection string from the Atlas dashboard:
|
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:
|
4. Add the connection string to your .env file:
|
||||||
${pc.dim('DATABASE_URL="your_connection_string"')}
|
${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"));
|
mainSpinner.stop(pc.red("MongoDB Atlas setup failed"));
|
||||||
consola.error(
|
consola.error(
|
||||||
pc.red(
|
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 pc from "picocolors";
|
||||||
import type { ProjectPackageManager } from "../types";
|
import type { ProjectPackageManager } from "../types";
|
||||||
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
||||||
|
import { type EnvVariable, addEnvVariablesToFile } from "./env-setup";
|
||||||
|
|
||||||
type NeonConfig = {
|
type NeonConfig = {
|
||||||
connectionString: string;
|
connectionString: string;
|
||||||
@@ -120,12 +121,16 @@ async function createNeonProject(
|
|||||||
|
|
||||||
async function writeEnvFile(projectDir: string, config?: NeonConfig) {
|
async function writeEnvFile(projectDir: string, config?: NeonConfig) {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||||
const envContent = config
|
const variables: EnvVariable[] = [
|
||||||
? `DATABASE_URL="${config.connectionString}"`
|
{
|
||||||
: `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
key: "DATABASE_URL",
|
||||||
|
value:
|
||||||
await fs.ensureDir(path.dirname(envPath));
|
config?.connectionString ??
|
||||||
await fs.writeFile(envPath, envContent);
|
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
|
||||||
|
condition: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await addEnvVariablesToFile(envPath, variables);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import pc from "picocolors";
|
|||||||
import type { ProjectPackageManager } from "../types";
|
import type { ProjectPackageManager } from "../types";
|
||||||
import { addPackageDependency } from "../utils/add-package-deps";
|
import { addPackageDependency } from "../utils/add-package-deps";
|
||||||
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
||||||
|
import { type EnvVariable, addEnvVariablesToFile } from "./env-setup";
|
||||||
|
|
||||||
type PrismaConfig = {
|
type PrismaConfig = {
|
||||||
databaseUrl: string;
|
databaseUrl: string;
|
||||||
@@ -72,27 +73,16 @@ async function initPrismaDatabase(
|
|||||||
async function writeEnvFile(projectDir: string, config?: PrismaConfig) {
|
async function writeEnvFile(projectDir: string, config?: PrismaConfig) {
|
||||||
try {
|
try {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||||
await fs.ensureDir(path.dirname(envPath));
|
const variables: EnvVariable[] = [
|
||||||
|
{
|
||||||
let envContent = "";
|
key: "DATABASE_URL",
|
||||||
if (await fs.pathExists(envPath)) {
|
value:
|
||||||
envContent = await fs.readFile(envPath, "utf8");
|
config?.databaseUrl ??
|
||||||
}
|
"postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
|
||||||
|
condition: true,
|
||||||
const databaseUrlLine = config
|
},
|
||||||
? `DATABASE_URL="${config.databaseUrl}"`
|
];
|
||||||
: `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;
|
await addEnvVariablesToFile(envPath, variables);
|
||||||
|
|
||||||
if (!envContent.includes("DATABASE_URL=")) {
|
|
||||||
envContent += `\n${databaseUrlLine}`;
|
|
||||||
} else {
|
|
||||||
envContent = envContent.replace(
|
|
||||||
/DATABASE_URL=.*(\r?\n|$)/,
|
|
||||||
`${databaseUrlLine}$1`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.writeFile(envPath, envContent.trim());
|
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
consola.error("Failed to update environment configuration");
|
consola.error("Failed to update environment configuration");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import fs from "fs-extra";
|
|||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import type { ProjectConfig, ProjectPackageManager } from "../types";
|
import type { ProjectConfig, ProjectPackageManager } from "../types";
|
||||||
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
import { getPackageExecutionCommand } from "../utils/get-package-execution-command";
|
||||||
|
import { type EnvVariable, addEnvVariablesToFile } from "./env-setup";
|
||||||
|
|
||||||
async function writeSupabaseEnvFile(
|
async function writeSupabaseEnvFile(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
@@ -13,38 +14,21 @@ async function writeSupabaseEnvFile(
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
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 =
|
const dbUrlToUse =
|
||||||
databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
||||||
|
const variables: EnvVariable[] = [
|
||||||
const databaseUrlLine = `DATABASE_URL="${dbUrlToUse}"`;
|
{
|
||||||
const directUrlLine = `DIRECT_URL="${dbUrlToUse}"`;
|
key: "DATABASE_URL",
|
||||||
|
value: dbUrlToUse,
|
||||||
if (!envContent.includes("DATABASE_URL=")) {
|
condition: true,
|
||||||
envContent += `\n${databaseUrlLine}`;
|
},
|
||||||
} else {
|
{
|
||||||
envContent = envContent.replace(
|
key: "DIRECT_URL",
|
||||||
/DATABASE_URL=.*(\r?\n|$)/,
|
value: dbUrlToUse,
|
||||||
`${databaseUrlLine}$1`,
|
condition: true,
|
||||||
);
|
},
|
||||||
}
|
];
|
||||||
|
await addEnvVariablesToFile(envPath, variables);
|
||||||
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;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
consola.error(pc.red("Failed to update .env file for Supabase."));
|
consola.error(pc.red("Failed to update .env file for Supabase."));
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import {
|
|||||||
} from "@clack/prompts";
|
} from "@clack/prompts";
|
||||||
import consola from "consola";
|
import consola from "consola";
|
||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
import fs from "fs-extra";
|
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
import { commandExists } from "../utils/command-exists";
|
import { commandExists } from "../utils/command-exists";
|
||||||
|
import { type EnvVariable, addEnvVariablesToFile } from "./env-setup";
|
||||||
|
|
||||||
type TursoConfig = {
|
type TursoConfig = {
|
||||||
dbUrl: string;
|
dbUrl: string;
|
||||||
@@ -138,7 +138,9 @@ async function createTursoDatabase(dbName: string, groupName: string | null) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
s.start(
|
s.start(
|
||||||
`Creating Turso database "${dbName}"${groupName ? ` in group "${groupName}"` : ""}...`,
|
`Creating Turso database "${dbName}"${
|
||||||
|
groupName ? ` in group "${groupName}"` : ""
|
||||||
|
}...`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (groupName) {
|
if (groupName) {
|
||||||
@@ -173,14 +175,19 @@ async function createTursoDatabase(dbName: string, groupName: string | null) {
|
|||||||
|
|
||||||
async function writeEnvFile(projectDir: string, config?: TursoConfig) {
|
async function writeEnvFile(projectDir: string, config?: TursoConfig) {
|
||||||
const envPath = path.join(projectDir, "apps/server", ".env");
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
||||||
const envContent = config
|
const variables: EnvVariable[] = [
|
||||||
? `DATABASE_URL="${config.dbUrl}"
|
{
|
||||||
DATABASE_AUTH_TOKEN="${config.authToken}"`
|
key: "DATABASE_URL",
|
||||||
: `DATABASE_URL=
|
value: config?.dbUrl ?? "",
|
||||||
DATABASE_AUTH_TOKEN=`;
|
condition: true,
|
||||||
|
},
|
||||||
await fs.ensureDir(path.dirname(envPath));
|
{
|
||||||
await fs.writeFile(envPath, envContent);
|
key: "DATABASE_AUTH_TOKEN",
|
||||||
|
value: config?.authToken ?? "",
|
||||||
|
condition: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
await addEnvVariablesToFile(envPath, variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayManualSetupInstructions() {
|
function displayManualSetupInstructions() {
|
||||||
@@ -288,7 +295,9 @@ export async function setupTurso(config: ProjectConfig): Promise<void> {
|
|||||||
setupSpinner.stop(pc.red("Failed to set up Turso database"));
|
setupSpinner.stop(pc.red("Failed to set up Turso database"));
|
||||||
consola.error(
|
consola.error(
|
||||||
pc.red(
|
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);
|
await writeEnvFile(projectDir);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ node_modules/
|
|||||||
# env
|
# env
|
||||||
.env*
|
.env*
|
||||||
.env.production
|
.env.production
|
||||||
|
!.env.example
|
||||||
.dev.vars
|
.dev.vars
|
||||||
|
|
||||||
# logs
|
# logs
|
||||||
|
|||||||
Reference in New Issue
Block a user