Skip to content

feat: single env file #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cli/src/installers/envVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {

const envContent = getEnvContent(!!usingAuth, !!usingPrisma);

const envSchemaFile =
const envFile =
usingAuth && usingPrisma
? "with-auth-prisma.mjs"
: usingAuth
Expand All @@ -18,13 +18,13 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {
? "with-prisma.mjs"
: "";

if (envSchemaFile !== "") {
if (envFile !== "") {
const envSchemaSrc = path.join(
PKG_ROOT,
"template/extras/src/env/schema",
envSchemaFile,
"template/extras/src/env",
envFile,
);
const envSchemaDest = path.join(projectDir, "src/env/schema.mjs");
const envSchemaDest = path.join(projectDir, "src/env.mjs");
fs.copySync(envSchemaSrc, envSchemaDest);
}

Expand All @@ -37,7 +37,7 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {

const getEnvContent = (usingAuth: boolean, usingPrisma: boolean) => {
let content =
"# When adding additional env variables, the schema in /env/schema.mjs should be updated accordingly";
"# When adding additional env variables, the schema in /env.mjs as well as the `processEnv` object should be updated accordingly";

if (usingPrisma)
content += `
Expand Down
2 changes: 1 addition & 1 deletion cli/template/base/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
* This is especially useful for Docker builds.
*/
!process.env.SKIP_ENV_VALIDATION && (await import("./src/env/server.mjs"));
!process.env.SKIP_ENV_VALIDATION && (await import("./src/env.mjs"));

/** @type {import("next").NextConfig} */
const config = {
Expand Down
87 changes: 87 additions & 0 deletions cli/template/base/src/env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// @ts-check
import { z } from "zod";

/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
const server = z.object({
NODE_ENV: z.enum(["development", "test", "production"]),
});

/**
* Specify your client-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
* To expose them to the client, prefix them with `NEXT_PUBLIC_`.
*/
const client = z.object({
// NEXT_PUBLIC_CLIENTVAR: z.string().min(1),
});

/**
* You can't destruct `process.env` as a regular object in the Next.js
* edge runtimes (e.g. middlewares) or client-side so we need to destruct manually.
* @type {Record<keyof z.infer<typeof server> | keyof z.infer<typeof client>, string | undefined>}
*/
const processEnv = {
NODE_ENV: process.env.NODE_ENV,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
};

// Don't touch the part below
// --------------------------

const merged = server.merge(client);
/** @type z.infer<merged>
* @ts-ignore - can't type this properly in jsdoc */
let env;

if (!process.env.SKIP_ENV_VALIDATION) {
const formatErrors = (
/** @type {z.ZodFormattedError<Map<string,string>,string>} */
errors,
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`;
})
.filter(Boolean);

const isServer = typeof window === "undefined";

const parsed = isServer
? merged.safeParse(processEnv) // on server we can validate all env vars
: client.safeParse(processEnv); // on client we can only validate the ones that are exposed

if (parsed.success === false) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(parsed.error.format()),
);
throw new Error("Invalid environment variables");
}

/** @type z.infer<merged>
* @ts-ignore - can't type this properly in jsdoc */
env = new Proxy(parsed.data, {
get(target, prop) {
if (typeof prop !== "string") return undefined;
// Throw a descriptive error if a server-side env var is accessed on the client
// Otherwise it would just be returning `undefined` and be annoying to debug
if (!isServer && !prop.startsWith("NEXT_PUBLIC_"))
throw new Error(
`❌ Attempted to access server-side environment variable '${prop}' on the client`,
);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - can't type this properly in jsdoc
return target[prop];
},
});
} else {
/** @type z.infer<merged>
* @ts-ignore - can't type this properly in jsdoc */
env = processEnv;
}

export { env };
35 changes: 0 additions & 35 deletions cli/template/base/src/env/client.mjs

This file was deleted.

38 changes: 0 additions & 38 deletions cli/template/base/src/env/schema.mjs

This file was deleted.

27 changes: 0 additions & 27 deletions cli/template/base/src/env/server.mjs

This file was deleted.

57 changes: 0 additions & 57 deletions cli/template/extras/src/env/schema/with-auth-prisma.mjs

This file was deleted.

55 changes: 0 additions & 55 deletions cli/template/extras/src/env/schema/with-auth.mjs

This file was deleted.

40 changes: 0 additions & 40 deletions cli/template/extras/src/env/schema/with-prisma.mjs

This file was deleted.

Loading