Files
create-better-t-stack/apps/cli/src/helpers/core/env-setup.ts

335 lines
8.0 KiB
TypeScript

import path from "node:path";
import fs from "fs-extra";
import type { ProjectConfig } from "../../types";
import { generateAuthSecret } from "./auth-setup";
export interface EnvVariable {
key: string;
value: string | null | undefined;
condition: boolean;
}
export async function addEnvVariablesToFile(
filePath: string,
variables: EnvVariable[],
) {
await fs.ensureDir(path.dirname(filePath));
let envContent = "";
if (await fs.pathExists(filePath)) {
envContent = await fs.readFile(filePath, "utf8");
}
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);
if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
modified = true;
}
} else {
contentToAdd += `${key}=${valueToWrite}\n`;
modified = true;
}
}
}
if (contentToAdd) {
if (envContent.length > 0 && !envContent.endsWith("\n")) {
envContent += "\n";
}
envContent += contentToAdd;
}
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(config: ProjectConfig) {
const {
backend,
frontend,
database,
auth,
examples,
dbSetup,
projectDir,
webDeploy,
serverDeploy,
} = config;
const hasReactRouter = frontend.includes("react-router");
const hasTanStackRouter = frontend.includes("tanstack-router");
const hasTanStackStart = frontend.includes("tanstack-start");
const hasNextJs = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");
const hasSvelte = frontend.includes("svelte");
const hasSolid = frontend.includes("solid");
const hasWebFrontend =
hasReactRouter ||
hasTanStackRouter ||
hasTanStackStart ||
hasNextJs ||
hasNuxt ||
hasSolid ||
hasSvelte;
if (hasWebFrontend) {
const clientDir = path.join(projectDir, "apps/web");
if (await fs.pathExists(clientDir)) {
let envVarName = "VITE_SERVER_URL";
let serverUrl = "http://localhost:3000";
if (hasNextJs) {
envVarName = "NEXT_PUBLIC_SERVER_URL";
} else if (hasNuxt) {
envVarName = "NUXT_PUBLIC_SERVER_URL";
} else if (hasSvelte) {
envVarName = "PUBLIC_SERVER_URL";
}
if (backend === "convex") {
if (hasNextJs) envVarName = "NEXT_PUBLIC_CONVEX_URL";
else if (hasNuxt) envVarName = "NUXT_PUBLIC_CONVEX_URL";
else if (hasSvelte) envVarName = "PUBLIC_CONVEX_URL";
else envVarName = "VITE_CONVEX_URL";
serverUrl = "https://<YOUR_CONVEX_URL>";
}
const clientVars: EnvVariable[] = [
{
key: envVarName,
value: serverUrl,
condition: true,
},
];
if (backend === "convex" && auth === "clerk") {
if (hasNextJs) {
clientVars.push(
{
key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
value: "",
condition: true,
},
{
key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
value: "",
condition: true,
},
{
key: "CLERK_SECRET_KEY",
value: "",
condition: true,
},
);
} else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
clientVars.push({
key: "VITE_CLERK_PUBLISHABLE_KEY",
value: "",
condition: true,
});
if (hasTanStackStart) {
clientVars.push({
key: "CLERK_SECRET_KEY",
value: "",
condition: true,
});
}
}
}
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
}
}
if (
frontend.includes("native-nativewind") ||
frontend.includes("native-unistyles")
) {
const nativeDir = path.join(projectDir, "apps/native");
if (await fs.pathExists(nativeDir)) {
let envVarName = "EXPO_PUBLIC_SERVER_URL";
let serverUrl = "http://localhost:3000";
if (backend === "convex") {
envVarName = "EXPO_PUBLIC_CONVEX_URL";
serverUrl = "https://<YOUR_CONVEX_URL>";
}
const nativeVars: EnvVariable[] = [
{
key: envVarName,
value: serverUrl,
condition: true,
},
];
if (backend === "convex" && auth === "clerk") {
nativeVars.push({
key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY",
value: "",
condition: true,
});
}
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
}
}
if (backend === "convex") {
return;
}
const serverDir = path.join(projectDir, "apps/server");
if (!(await fs.pathExists(serverDir))) {
return;
}
const envPath = path.join(serverDir, ".env");
let corsOrigin = "http://localhost:3001";
if (hasReactRouter || hasSvelte) {
corsOrigin = "http://localhost:5173";
}
let databaseUrl: string | null = null;
if (database !== "none" && dbSetup === "none") {
switch (database) {
case "postgres":
databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
break;
case "mysql":
databaseUrl = "mysql://root:password@localhost:3306/mydb";
break;
case "mongodb":
databaseUrl = "mongodb://localhost:27017/mydatabase";
break;
case "sqlite":
if (config.runtime === "workers") {
databaseUrl = "http://127.0.0.1:8080";
} else {
databaseUrl = "file:./local.db";
}
break;
}
}
const serverVars: EnvVariable[] = [
{
key: "CORS_ORIGIN",
value: corsOrigin,
condition: true,
},
{
key: "BETTER_AUTH_SECRET",
value: generateAuthSecret(),
condition: !!auth,
},
{
key: "BETTER_AUTH_URL",
value: "http://localhost:3000",
condition: !!auth,
},
{
key: "DATABASE_URL",
value: databaseUrl,
condition: database !== "none" && dbSetup === "none",
},
{
key: "GOOGLE_GENERATIVE_AI_API_KEY",
value: "",
condition: examples?.includes("ai") || false,
},
];
await addEnvVariablesToFile(envPath, serverVars);
const isUnifiedAlchemy =
webDeploy === "alchemy" && serverDeploy === "alchemy";
const isIndividualAlchemy =
webDeploy === "alchemy" || serverDeploy === "alchemy";
if (isUnifiedAlchemy) {
const rootEnvPath = path.join(projectDir, ".env");
const rootAlchemyVars: EnvVariable[] = [
{
key: "ALCHEMY_PASSWORD",
value: "please-change-this",
condition: true,
},
];
await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
} else if (isIndividualAlchemy) {
if (webDeploy === "alchemy") {
const webDir = path.join(projectDir, "apps/web");
if (await fs.pathExists(webDir)) {
const webAlchemyVars: EnvVariable[] = [
{
key: "ALCHEMY_PASSWORD",
value: "please-change-this",
condition: true,
},
];
await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
}
}
if (serverDeploy === "alchemy") {
const serverDir = path.join(projectDir, "apps/server");
if (await fs.pathExists(serverDir)) {
const serverAlchemyVars: EnvVariable[] = [
{
key: "ALCHEMY_PASSWORD",
value: "please-change-this",
condition: true,
},
];
await addEnvVariablesToFile(
path.join(serverDir, ".env"),
serverAlchemyVars,
);
}
}
}
}