convex
Version:
Client for the Convex Cloud
210 lines (193 loc) • 5.36 kB
text/typescript
/**
* Help the developer store the CONVEX_URL environment variable.
*/
import chalk from "chalk";
import * as dotenv from "dotenv";
import inquirer from "inquirer";
import { Context } from "./context";
import { loadPackageJson } from "./utils";
export async function suggestedEnvVarName(ctx: Context): Promise<{
appearToBeUsing?: string;
envVar: string;
}> {
// no package.json, that's fine, just guess
if (!ctx.fs.exists("package.json")) {
return {
envVar: "CONVEX_URL",
};
}
const packages = await loadPackageJson(ctx);
// Is it create-react-app?
const isCreateReactApp = !!packages.filter(
({ name }) => name === "react-scripts"
).length;
if (isCreateReactApp) {
return {
appearToBeUsing: "create-react-app",
envVar: "REACT_APP_CONVEX_URL",
};
}
const isNextJs = !!packages.filter(({ name }) => name === "next").length;
if (isNextJs) {
return {
appearToBeUsing: "Next.js",
envVar: "NEXT_PUBLIC_CONVEX_URL",
};
}
const isVite = !!packages.filter(({ name }) => name === "vite");
if (isVite) {
return {
appearToBeUsing: "Vite",
envVar: "VITE_CONVEX_URL",
};
}
return {
envVar: "CONVEX_URL",
};
}
function suggestedProdEnvFile(ctx: Context): {
existing: boolean;
envFile: string;
} {
// The most prod-looking env file that exists, or .env
if (ctx.fs.exists(".env.production")) {
return {
existing: true,
envFile: ".env.production",
};
}
if (ctx.fs.exists(".env")) {
return {
existing: true,
envFile: ".env",
};
}
return {
existing: false,
envFile: ".env",
};
}
function suggestedDevEnvFile(ctx: Context): {
existing: boolean;
envFile: string;
} {
// The most dev-looking env file that exists, or .env.local
return {
existing: ctx.fs.exists(".env.local"),
envFile: ".env.local",
};
}
const EXPECTED_NAMES = new Set([
"CONVEX_URL",
"NEXT_PUBLIC_CONVEX_URL",
"VITE_CONVEX_URL",
"REACT_APP_CONVEX_URL",
]);
export async function offerToWriteToEnv(
ctx: Context,
type: "dev" | "prod",
value: string,
saveUrl: "yes" | "no" | "ask" = "ask" as const
) {
if (saveUrl === "no") return;
const { envFile, existing } =
type === "dev" ? suggestedDevEnvFile(ctx) : suggestedProdEnvFile(ctx);
if (existing) {
const config = dotenv.parse(ctx.fs.readUtf8File(envFile));
console.error(`Found ${envFile} file.`);
const matching = Object.keys(config).filter(key => EXPECTED_NAMES.has(key));
if (matching.length > 1) {
console.error(
chalk.yellow(
`Found multiple CONVEX_URL environment variables in ${envFile} so cannot update automatically.`
)
);
return;
}
if (matching.length === 1) {
const [envVar, oldValue] = [matching[0], config[matching[0]]];
if (oldValue === value) {
console.error(
chalk.green(`${envVar} in ${envFile} is already ${value}`)
);
return;
}
if (Object.values(config).filter(v => v === oldValue).length !== 1) {
chalk.yellow(`Can't safely modify ${envFile}, please edit manually.`);
return;
}
if (
saveUrl === "yes" ||
(
await inquirer.prompt([
{
type: "confirm",
name: "updateEnvFile",
message: `Update ${envVar} in ${envFile} to ${value} to save ${type} URL?`,
default: true,
},
])
).updateEnvFile
) {
const modified = ctx.fs.readUtf8File(envFile).replace(oldValue, value);
ctx.fs.writeUtf8File(envFile, modified);
console.error(
chalk.green(`Updated ${envFile} with ${envVar}="${value}"`)
);
return;
}
console.error(
chalk.yellow(`Please update ${envFile} with ${envVar}="${value}"`)
);
return;
}
const { appearToBeUsing, envVar } = await suggestedEnvVarName(ctx);
if (appearToBeUsing) {
console.error(`Detected ${appearToBeUsing} project.`);
}
if (
saveUrl === "yes" ||
(
await inquirer.prompt([
{
type: "confirm",
name: "updateEnvFile",
message: `Add ${envVar} to ${envFile} to save ${type} URL?`,
default: true,
},
])
).updateEnvFile
) {
const orig = ctx.fs.readUtf8File(envFile);
const modified = `${orig}\n${envVar}="${value}"\n`;
ctx.fs.writeUtf8File(envFile, modified);
console.error(
chalk.green(`Updated ${envFile} with ${envVar}="${value}"`)
);
}
return;
}
// Finally, offer to create a new .env file
const { appearToBeUsing, envVar } = await suggestedEnvVarName(ctx);
if (appearToBeUsing) {
console.error(chalk.green(`Detected ${appearToBeUsing} project.`));
}
if (
saveUrl === "yes" ||
(
await inquirer.prompt([
{
type: "confirm",
name: "updateEnvFile",
message: `Create a ${envFile} file and add ${envVar} to save ${type} URL?`,
default: true,
},
])
).updateEnvFile
) {
const contents = `${envVar}="${value}"\n`;
ctx.fs.writeUtf8File(envFile, contents);
console.error(chalk.green(`Wrote ${envFile} file to save ${envVar}.`));
return;
}
}