From 849745110c8dfbe3b6214d42f8349b3bc894462d Mon Sep 17 00:00:00 2001 From: Jacob Devore Date: Sun, 15 Jan 2023 22:32:54 -0500 Subject: [PATCH] update readme --- README.md | 165 ++++++++++++------------------------------------------ 1 file changed, 36 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index b4af6d1..89ca222 100644 --- a/README.md +++ b/README.md @@ -55,32 +55,28 @@ Where your server and client schemas live for typesafe environment variables ```js //@ts-check import { validateEnvironmentVariables } from "next-validenv"; + 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. - * @template {ReturnType} T - * @param {T} serverSchema - * @returns {z.infer} + * Specify your environment variables schema here. + * This way, you can ensure the app isn't built with invalid environment variables. + * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. + * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- */ -export const serverSchema = z.object({ +export const schema = 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_`. - * @template {ReturnType} T - * @param {T} serverSchema - * @returns {z.infer} + * Environment variable declarations based on the schema help structure your environment variables programmatically. + * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} */ -export const clientSchema = z.object({ - // NEXT_PUBLIC_TEST: z.string(), -}); +export const env = { + NODE_ENV: process.env.NODE_ENV, +}; -export const env = validateEnvironmentVariables(serverSchema, clientSchema); +validateEnvironmentVariables(schema, env); ``` ### Update ~~`next.config.js`~~ to `next.config.mjs` @@ -100,7 +96,7 @@ const config = { export default config; ``` -### Create `environment.d.ts` +### Create `env.d.ts` ```js import { env } from "./env.mjs"; @@ -161,141 +157,52 @@ Where your server and client schemas live for typesafe environment variables 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. - * @template {ReturnType} T - * @param {T} serverSchema - * @returns {z.infer} + * Specify your environment variables schema here. + * This way, you can ensure the app isn't built with invalid environment variables. + * By default, environment variables are only available in the Node.js environment, meaning they won't be exposed to the browser. In order to expose a variable to the browser you have to prefix the variable with NEXT_PUBLIC_. + * -> Don't use any Zod .transform() methods in the schema (will cause `implicitly has type 'any'` error) <- */ -export const serverSchema = z.object({ +export const schema = 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_`. - * @template {ReturnType} T - * @param {T} serverSchema - * @returns {z.infer} + * Environment variable declarations based on the schema help structure your environment variables programmatically. + * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} */ -export const clientSchema = z.object({ - // NEXT_PUBLIC_TEST: z.string(), -}); +export const env = { + NODE_ENV: process.env.NODE_ENV, +}; /** * -------------------------------- * -------------------------------- - * DON'T TOUCH ANYTHING BELOW THIS LINE (UNLESS YOU KNOW WHAT YOU'RE DOING) + * Next-ValidEnv Manual Implementation * -------------------------------- * -------------------------------- */ -// maps through zod schema keys and returns an object with the safeParse values from process.env[key] -export const mapEnvironmentVariablesToObject = ( - /** @type {ReturnType} */ schema -) => { - /** @type {{ [key: string]: string | undefined; }} */ - let env = {}; - - Object.keys(schema.shape).forEach((key) => (env[key] = process.env[key])); - - return schema.safeParse(env); -}; - export const formatZodErrors = ( - /** @type {import('zod').ZodFormattedError,string>} */ errors + /** @type z.ZodFormattedError, string> */ errors ) => Object.entries(errors) .map(([name, value]) => { if (value && "_errors" in value) return `${name}: ${value._errors.join(", ")}\n`; + + return; }) .filter(Boolean); -export const formatErrors = (/** @type {(string | undefined)[]} */ errors) => - errors.map((name) => `${name}\n`); - -/** - * @function - * @template {ReturnType} T - * @template {ReturnType} K - * @param {T} serverSchema - * @param {K} clientSchema - * @returns {z.infer & z.infer} - */ -export const validateEnvironmentVariables = (serverSchema, clientSchema) => { - let serverEnv = mapEnvironmentVariablesToObject(serverSchema); - let clientEnv = mapEnvironmentVariablesToObject(clientSchema); - - // holds not set environment variable errors for both client and server - /** @type {(string | undefined)[]} */ let invalidEnvErrors = []; - - if (!serverEnv.success) { - invalidEnvErrors = [ - ...invalidEnvErrors, - ...formatZodErrors(serverEnv.error.format()), - ]; - } - - if (!clientEnv.success) { - invalidEnvErrors = [ - ...invalidEnvErrors, - ...formatZodErrors(clientEnv.error.format()), - ]; - } +const safeParsedEnv = schema.safeParse(env); - // if one or more environment variables are not set, throw an error - if (!serverEnv.success || !clientEnv.success) { - console.error("❌ Invalid environment variables:\n", ...invalidEnvErrors); - throw new Error("Invalid environment variables"); - } - - // holds server environment variables errors that are exposed to the client - /** @type {(string | undefined)[]} */ let exposedServerEnvErrors = []; - - for (let key of Object.keys(serverEnv.data)) { - if (key.startsWith("NEXT_PUBLIC_")) { - exposedServerEnvErrors = [...exposedServerEnvErrors, key]; - } - } - - // if one or more server environment variables are exposed to the client, throw an error - if (exposedServerEnvErrors.length > 0) { - console.error( - "❌ You are exposing the following server-side environment variables to the client:\n", - ...formatErrors(exposedServerEnvErrors) - ); - throw new Error( - "You are exposing the following server-side environment variables to the client" - ); - } - - // holds client environment variables errors that are not exposed to the client - /** @type {(string | undefined)[]} */ let notExposedClientEnvErrors = []; - - for (let key of Object.keys(clientEnv.data)) { - if (!key.startsWith("NEXT_PUBLIC_")) { - notExposedClientEnvErrors = [...notExposedClientEnvErrors, key]; - } - } - - // if one or more client environment variables are not exposed to the client, throw an error - if (notExposedClientEnvErrors.length > 0) { - console.error( - "❌ All client-side environment variables must begin with 'NEXT_PUBLIC_', you are not exposing the following:\n", - ...formatErrors(notExposedClientEnvErrors) - ); - throw new Error( - "All client-side environment variables must begin with 'NEXT_PUBLIC_', you are not exposing the following:" - ); - } - - // return both client and server environment variables - return { ...serverEnv.data, ...clientEnv.data }; -}; - -export const env = validateEnvironmentVariables(serverSchema, clientSchema); +if (!safeParsedEnv.success) { + console.error( + "❌ Invalid environment variables:\n", + ...formatZodErrors(safeParsedEnv.error.format()) + ); + throw new Error("Invalid environment variables"); +} ``` ### Update ~~`next.config.js`~~ to `next.config.mjs` @@ -315,7 +222,7 @@ const config = { export default config; ``` -### Create `environment.d.ts` +### Create `env.d.ts` ```js import { env } from "./env.mjs";