TODOs: - [x] Do restart when checkout / restore if there is a DB - [x] List all branches (branch id, name, date) - [x] Allow checking out versions with no DB - [x] safeguard to never delete main branches - [x] create app hook for neon template - [x] weird UX with connector on configure panel - [x] tiny neon logo in connector - [x] deploy to vercel - [x] build forgot password page - [x] what about email setup - [x] lots of imgix errors - [x] edit file - db snapshot - [x] DYAD_DISABLE_DB_PUSH - [ ] update portal doc - [x] switch preview branch to be read-only endpoint - [x] disable supabase sys prompt if neon is enabled - [ ] https://payloadcms.com/docs/upload/storage-adapters - [x] need to use main branch... Phase 2? - [x] generate DB migrations
174 lines
4.7 KiB
TypeScript
174 lines
4.7 KiB
TypeScript
/**
|
|
* DO NOT USE LOGGER HERE.
|
|
* Environment variables are sensitive and should not be logged.
|
|
*/
|
|
|
|
import { getDyadAppPath } from "@/paths/paths";
|
|
import { EnvVar } from "../ipc_types";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import log from "electron-log";
|
|
|
|
const logger = log.scope("app_env_var_utils");
|
|
|
|
export const ENV_FILE_NAME = ".env.local";
|
|
|
|
function getEnvFilePath({ appPath }: { appPath: string }): string {
|
|
return path.join(getDyadAppPath(appPath), ENV_FILE_NAME);
|
|
}
|
|
|
|
export async function updatePostgresUrlEnvVar({
|
|
appPath,
|
|
connectionUri,
|
|
}: {
|
|
appPath: string;
|
|
connectionUri: string;
|
|
}) {
|
|
// Given the connection uri, update the env var for POSTGRES_URL
|
|
const envVars = parseEnvFile(await readEnvFile({ appPath }));
|
|
|
|
// Find existing POSTGRES_URL or add it if it doesn't exist
|
|
const existingVar = envVars.find((envVar) => envVar.key === "POSTGRES_URL");
|
|
if (existingVar) {
|
|
existingVar.value = connectionUri;
|
|
} else {
|
|
envVars.push({
|
|
key: "POSTGRES_URL",
|
|
value: connectionUri,
|
|
});
|
|
}
|
|
|
|
const envFileContents = serializeEnvFile(envVars);
|
|
await fs.promises.writeFile(getEnvFilePath({ appPath }), envFileContents);
|
|
}
|
|
|
|
export async function updateDbPushEnvVar({
|
|
appPath,
|
|
disabled,
|
|
}: {
|
|
appPath: string;
|
|
disabled: boolean;
|
|
}) {
|
|
try {
|
|
// Try to read existing env file
|
|
let envVars: EnvVar[];
|
|
try {
|
|
const content = await readEnvFile({ appPath });
|
|
envVars = parseEnvFile(content);
|
|
} catch {
|
|
// If file doesn't exist, start with empty array
|
|
envVars = [];
|
|
}
|
|
|
|
// Update or add DYAD_DISABLE_DB_PUSH
|
|
const existingVar = envVars.find(
|
|
(envVar) => envVar.key === "DYAD_DISABLE_DB_PUSH",
|
|
);
|
|
if (existingVar) {
|
|
existingVar.value = disabled ? "true" : "false";
|
|
} else {
|
|
envVars.push({
|
|
key: "DYAD_DISABLE_DB_PUSH",
|
|
value: disabled ? "true" : "false",
|
|
});
|
|
}
|
|
|
|
const envFileContents = serializeEnvFile(envVars);
|
|
await fs.promises.writeFile(getEnvFilePath({ appPath }), envFileContents);
|
|
} catch (error) {
|
|
logger.error(
|
|
`Failed to update DB push environment variable for app ${appPath}: ${error}`,
|
|
);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function readPostgresUrlFromEnvFile({
|
|
appPath,
|
|
}: {
|
|
appPath: string;
|
|
}): Promise<string> {
|
|
const contents = await readEnvFile({ appPath });
|
|
const envVars = parseEnvFile(contents);
|
|
const postgresUrl = envVars.find(
|
|
(envVar) => envVar.key === "POSTGRES_URL",
|
|
)?.value;
|
|
if (!postgresUrl) {
|
|
throw new Error("POSTGRES_URL not found in .env.local");
|
|
}
|
|
return postgresUrl;
|
|
}
|
|
|
|
export async function readEnvFile({
|
|
appPath,
|
|
}: {
|
|
appPath: string;
|
|
}): Promise<string> {
|
|
return fs.promises.readFile(getEnvFilePath({ appPath }), "utf8");
|
|
}
|
|
|
|
// Helper function to parse .env.local file content
|
|
export function parseEnvFile(content: string): EnvVar[] {
|
|
const envVars: EnvVar[] = [];
|
|
const lines = content.split("\n");
|
|
|
|
for (const line of lines) {
|
|
const trimmedLine = line.trim();
|
|
|
|
// Skip empty lines and comments
|
|
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
continue;
|
|
}
|
|
|
|
// Parse key=value pairs
|
|
const equalIndex = trimmedLine.indexOf("=");
|
|
if (equalIndex > 0) {
|
|
const key = trimmedLine.substring(0, equalIndex).trim();
|
|
const value = trimmedLine.substring(equalIndex + 1).trim();
|
|
|
|
// Handle quoted values with potential inline comments
|
|
let cleanValue = value;
|
|
if (value.startsWith('"')) {
|
|
// Find the closing quote, handling escaped quotes
|
|
let endQuoteIndex = -1;
|
|
for (let i = 1; i < value.length; i++) {
|
|
if (value[i] === '"' && value[i - 1] !== "\\") {
|
|
endQuoteIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (endQuoteIndex !== -1) {
|
|
cleanValue = value.slice(1, endQuoteIndex);
|
|
// Unescape escaped quotes
|
|
cleanValue = cleanValue.replace(/\\"/g, '"');
|
|
}
|
|
} else if (value.startsWith("'")) {
|
|
// Find the closing quote for single quotes
|
|
const endQuoteIndex = value.indexOf("'", 1);
|
|
if (endQuoteIndex !== -1) {
|
|
cleanValue = value.slice(1, endQuoteIndex);
|
|
}
|
|
}
|
|
// For unquoted values, keep everything as-is (including potential # symbols)
|
|
|
|
envVars.push({ key, value: cleanValue });
|
|
}
|
|
}
|
|
|
|
return envVars;
|
|
}
|
|
|
|
// Helper function to serialize environment variables to .env.local format
|
|
export function serializeEnvFile(envVars: EnvVar[]): string {
|
|
return envVars
|
|
.map(({ key, value }) => {
|
|
// Add quotes if value contains spaces or special characters
|
|
const needsQuotes = /[\s#"'=&?]/.test(value);
|
|
const quotedValue = needsQuotes
|
|
? `"${value.replace(/"/g, '\\"')}"`
|
|
: value;
|
|
return `${key}=${quotedValue}`;
|
|
})
|
|
.join("\n");
|
|
}
|