Giter Site home page Giter Site logo

t3-env's People

Contributors

albinkc avatar ari-becker avatar chungweileong94 avatar cliftonz avatar felipeemos avatar femaffezzolli avatar github-actions[bot] avatar hugohabicht01 avatar hyoban avatar jasongerbes avatar jgchk avatar jonasdoesthings avatar juliusmarminge avatar just-moh-it avatar mbifulco avatar nexxeln avatar ojdomela avatar parnavh avatar sameerjadav avatar stefanprobst avatar sturlen avatar theodevoid avatar veritem avatar willem-jaap avatar zamiell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

t3-env's Issues

Next.js apps using `output: "export"` should prevent you passing `server` to `createEnv()`

Hi ๐Ÿ‘‹๐Ÿป

Been working on a project where we're using Next.js with output: "export" and t3-env and thought it would be great if we could remove the server field from createEnv() altogether or better yet have it error if you try to pass it in at all.

This is more or less the opposite of what's been mentioned in #57 which could be equally useful.

As t3-env has more support for other frameworks, it's possible they too don't have server environment.

Could this tool be improved to warn new devs about potential bad anti-patterns Astro SSR?

I think I found a way to leak variables to the client in Astro using Islands with client:only...

  • Throw if non-client variables are leaked inside serialised props. Obviously this allows server variables if code is run on server to generate HTML. However now I can just add a server variable as an prop for an UI Component and it will show up in plain text in the HTML. This is an leak, which should be prevented.
  • If I import the validation function inside a UI Component and then use client:only island, I will get errors and no render if I try to access an server variable, which is expected. But the validation function is in the js island chunk. So some can open dev tools, look at the js chunk and find the variables.

Originally posted by @alexanderniebuhr in #60 (comment)

Question: What is the rationale for recommending variables validation on build?

In the documentation there is a section called Validate schema on build (recommended). I'm wondering why it should be recommended to do it at build time?

The reason to read environment variables at runtime (or when the server process starts) is to avoid doing a build for every environment. For commertial projects it is common to have multiple environments where an app is deployed to. It is quicker and more robust to build only once and use the resulting build artifact for all deployments to those environments.

How are you solving such situations?

Thank you for creating this library!

Vitest with @t3-oss/env-nextjs

I would like to test with Vitest and NextJS, unfortunately there are some errors:

Error: Invalid environment variables
 โฏ onValidationError node_modules/@t3-oss/core/index.ts:184:13
 โฏ h node_modules/@t3-oss/core/index.ts:196:12
 โฏ Module.P node_modules/@t3-oss/env-nextjs/index.ts:73:10
 โฏ env.mjs:4:20
      2| import { z } from "zod";
      3|
      4| export const env = createEnv(

As far as I understand I need to ovverride the default VITE_ env prefix somewhere in the vitest.config.js.

export default defineConfig({
  plugins: [react()],
  test: {
    setupFiles: '@t3-oss/env-nextjs', // here
  },

Could somebody help me how to set properly the setupFiles row with t3-env?

Validating environment variables with Astro Server-side Rendering

Note: The following is kind of like a recipe page awaiting its debut in the t3-env docs. Please consider this a proposal before I (or someone else) do so.

When Astro is built with the default Static Site Generation (SSG), the validation will happen on each static route generation at the place of env usage. Technically this is "Validation on build".

However, with Astro Server-side Rendering (SSR), validation takes a different "route" (no pun intended). If we just switch from SSG to SSR with npm astro add node for example, immediately we will see that no validation is run when running npm run build. Let's force it by importing the validation file in astro.config.ts:

import "./src/env-validate";

// ... or if delaying the validation for later on in the file:
await import("./src/env-validate");

Now npm run build will tell us that our environment variables are nowhere to be found in import.meta.env. How comes?

With Astro SSR, you have 1 chance at running the validation at build time, which is the moment when astro.config.ts is evaluated at the beginning of the build. Unfortunately import.meta.env at this "config reading time" is not populated yet, so you have to fallback to process.env or reading the .env files manually with Vite's loadEnv function. Thus putting import.meta.env into runtimeEnv doesn't work anymore with Astro SSR.

When working with Astro SSR, because there are the build-time and the runtime, there exists different types of environment variables. Here is one way to do it in Astro SSR, Node.js runtime. Note that each runtime has different ways of configuring t3-env, I'm just providing one of the Node runtime solutions.

// astro.config.ts

// ...

// Formulate the BUILDVERSION env var if not available
if (!process.env.BUILDVERSION) {
  console.log("Generating build version...");
  const lastCommitHash = execSync("git rev-parse --short HEAD")
    .toString()
    .slice(0, -1);
  const lastCommitTime = Math.floor(
    Number(
      new Date(execSync("git log -1 --format=%cd ").toString()).getTime() /
        60000
    )
  );
  process.env.BUILDVERSION = `${lastCommitTime}/${lastCommitHash}`;
}

// Assign .env to the fileEnv object. See fileEnv.ts for more details.
Object.assign(fileEnv, loadEnv(import.meta.env.MODE, process.cwd(), ""));

// Validate the environment variables
await import("./src/t3-env");

export default defineConfig({
  // ...
  output: "server",
  adapter: node({
    // ...
  }),
  vite: {
    define: {
      "process.env.BUILDVERSION": JSON.stringify(process.env.BUILDVERSION),
    },
  },
});
// fileEnv.ts
// (Server-side only) This file contains an exported object that will be
// assigned the content of the .env files (if any) by the astro.config.mjs file.
//
// The purpose is to use variables in the .env file in the
// ./src/env-validate.ts file without that file having to use Vite's `loadEnv`
// function, causing the `fsevents` module to be bundled in the client-side
// bundle.
const fileEnv: Record<string, string> = {};

export default fileEnv;
// t3-env.ts
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

import fileEnv from "./fileEnv";

const isServer = typeof window === "undefined";
const isBuilding = isServer && process.env.BUILDING === "true";

export const serverValidation = {
  build: {
    // Put in here any server-side variable that is hardcoded on build, i.e.
    // defined with Vite config's `define` option.
    BUILDVERSION: z.string().nonempty(),
  },
  runtime: {
    // Put in here any server-side variable that is read at runtime, i.e. being
    // read from `process.env`.
    NODE_ENV: z.enum(["development", "test", "production"]),
    AUTOCOMPLETE_HOST: z.string().nonempty(),
    API_HOST: z.string().nonempty(),
    APP_DOMAIN: z.string().nonempty(),
  },
};

export const env = createEnv({
  server: (isBuilding
    ? serverValidation.build
    : {
        ...serverValidation.build,
        ...serverValidation.runtime,
      }) as typeof serverValidation.build & typeof serverValidation.runtime,

  clientPrefix: "PUBLIC_",
  // The client variables are hardcoded at build time and can only be read from
  // `import\u002meta.env`. No `process.env` available on client - there is no
  // runtime client variable.
  client: {
    PUBLIC_EXAMPLE_HARDCODED_CLIENT_VAR: z.string().nonempty(),
  },

  runtimeEnv: {
    ...fileEnv, // For development, loading from .env and the environment

    // After build, import\u002meta.env contains:
    // - Client variables
    // - (Only on the server version of the bundle) Any env var that is present
    // in the code and is indeed present in the environment (at the build
    // time). If it is used in the code but doesn't exist in the environment,
    // it won't be included here. For example, if the environment has a
    // variable named `VARIABLE` and in the code there is an exact match of
    // `VARIABLE` (even `abcVARIABLEabc`), then this object will be like `{
    // VARIABLE: process.env.VARIABLE }`.
    //
    // Example after build:
    // ```
    // // Server version (by Astro's vite-plugin-env)
    // Object.assign({ PUBLIC_VAR: "abc"}, { BUILDING: process.env.BUILDING })`
    //
    // // Client version (by Vite)
    // { PUBLIC_VAR: "abc" }
    // ```
    //
    // See https://github.com/withastro/astro/blob/main/packages/astro/src/vite-plugin-env/index.ts
    //
    // The purpose is to continue fetching these vars from the environment at
    // runtime.
    //
    // Note that if the variables are:
    // - In the .env file (instead of in the environment), or
    // - Defined in the Vite's `define` option with `process.env.XXX`
    // then they will be included in the bundle at build time. The reason for
    // the former is... they are defined that way by Astro's vite-plugin-env.
    // The reason for the latter is, after it becomes `process.env.XXX`, Vite's
    // `define` will go and replace that with something else.
    //
    // Example: Server bundle after build (note the `BUILDVERSION`):
    // ```
    // Object.assign({ PUBLIC_VAR: "abc"}, { BUILDVERSION: "asdasd", BUILDING: process.env.BUILDING })`.
    // ```
    //
    // As for our use case, we use this object for client variables and
    // build-time server-side variables (by defining `process.env.XXX` via
    // Vite's `define`).
    ...import.meta.env,

    // The above object just provides client-side variables and build-time
    // server-side variables. The below is the runtime environment, providing
    // runtime server-side variables.
    //
    // We don't want the client to touch `process`, hence the `isServer`.
    ...(isServer ? process.env : {}),
  },
});

Should we allow `NODE_ENV` access on client?

With NextJS (probably same for most of the framework nowadays), only envs with PREFIX_ can be accessed in client-side, however, NODE_ENV is technically accessible in browser too.

So, the question is, should the library expose the NODE_ENV to the client-side?
One of the use-cases that I used personally is to show the Tailwind screen size indicator in NODE_ENV === 'development'. As of now, I can work around the limitation by creating a NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV in runtimeEnv, but it feels weird.

Refactor Documentation with Contentlayer Integration

Currently, docs are using next/mdx which makes the file structure complicated with docs/slug/page.mdx. I don't think this is the best way to organize our documentation.

To fix this, I suggest we use Contentlayer instead. This will give us a lot of benefits, like:

  • Validation
  • Type-safety
  • A cleaner codebase
  • A better developer experience
  • Custom SEO (head) for each document

Currently, we are limited to using the same head for each document, which is not ideal for SEO. With Contentlayer, we can have a head for each document, which will improve SEO.

I believe this change will be very helpful for the project. If the maintainers agree, I am willing to start working on it. Thank you for considering my proposal.

Server variable names and schemas are exposed to the client

As it's currently implemented, both server and client code refer to the same instance of t3-env (the output of createEnv())

This means that using T3-env leaks the following information about your server-side environment variables in your client-side code:

  • The names of server variables
  • Zod validation schemas for those variables

While this is nowhere near as bad as the values themselves being leaked, I think that is still some cause for concern.

I think there is at least some potential for consumers of this library to unknowingly leak secret sever-side implementation details, not realizing that server variable names and zod schemas will be visible in their client code.


I wonder if there's a way to split up the client and server envs so that the server code can be tree-shaken out of the client bundle? If it's even possible, it would probably be a drastic change to the API though.

If the API cannot be changed to avoid leaking server info to the client, it would probably make sense to add a note in the documentation warning users that this information will end up in their client JS.

Run build time environment variables validations using Vitejs

Problem

I can't get build-time validation working on a vitejs app. I want to run the schema validation on build time so I can check if all required environment variables are defined or not.

Repro

  1. Generate a TS React app using npm create vite@latest
  2. Run npm install @t3-oss/env-core zod
  3. Create a .env file with the following content

VITE_TEST_VAR="ABC"
VITE_MISSING="123"

  1. Create the @t3-oss/env-core required env.ts file with the following content
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

export const env = createEnv({
  /*
   * Specify what prefix the client-side variables must have.
   * This is enforced both on type-level and at runtime.
   */
  clientPrefix: "VITE_",
  client: {
    VITE_TEST_VAR: z.string(),
    VITE_MISSING: z.string(), // I used this variable to test the build time validation works
  },
  /**
   * What object holds the environment variables at runtime.
   * Often `process.env` or `import.meta.env`
   */
  runtimeEnv: import.meta.env,
});
  1. Import the exported env object from the t3 env file inside App.tsx (so we verify that env vars are actually usable within the client app)
// App.tsx
import "./App.css";
import { env } from "./env.ts";

function App() {
  return (
    <>
      <h1>{env.VITE_TEST_VAR}</h1>
    </>
  );
}

export default App;

  1. Run "npm run build" and the build should fail with the following error message
โŒ Invalid environment variables: { VITE_TEST_VAR: [ 'Required' ], VITE_MISSING: [ 'Required' ] }
failed to load config from /Users/user/dev/project/vite.config.ts
error during build:
Error: Invalid environment variables
    at f (file:///Users/user/dev/project/node_modules/@t3-oss/env-core/dist/index.mjs:1:426)
    at g (file:///Users/user/dev/project/node_modules/@t3-oss/env-core/dist/index.mjs:1:616)
    at file:///Users/user/dev/project/vite.config.ts.timestamp-1686946000230-87681b7d7ccd5.mjs:8:11
    at ModuleJob.run (node:internal/modules/esm/module_job:192:25)
error Command failed with exit code 1.

consider allowing `string | boolean | undefined` for `runtimeEnvStrict`

when using this package with nuxt to type VITE_ env vars, i am running into the following issue: nuxt types import.meta.env as Record<string, string | boolean | undefined> (see here) - i am guessing because vite sets these default values. however, t3-env enforces runtimeEnvStrict to be Record<string, string | undefined>. would you consider allowing Record<string, string | boolean | undefined> on runtimeEnvStrict to cover this usecase?

Invalid environment variables in CI environment running tests

Once it got to my CI, the validation failed -- and whats weird is it ran successfully locally. Would be nice if it failed nicely in a CI environment, but also my test shouldn't have accessed a env variable.

$ npm test
> /[email protected] test
> mocha --require ts-node/register --timeout 100000 --require ./dist/test-setup.js './src/**/*.spec.js' './src/**/*.spec.ts'
โŒ Invalid environment variables: {
  ...
}
Error: Invalid environment variables
    at /project/node_modules/@t3-oss/env-core/index.ts:155:1
    at g (/project/node_modules/@t3-oss/env-core/index.ts:155:1)
    at Object.<anonymous> (/project/src/env.ts:5:29)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/project/src/victorops/api.ts:14:1)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/project/src/data/Team.ts:3:1)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .ts] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/project/src/consts.js:2:1)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/project/src/puppet/consts.js:39:6)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/project/src/puppet/test/controller.spec.js:2:16)
    at Module._compile (node:internal/modules/cjs/loader:1097:14)
    at Module.m._compile (/project/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1149:10)
    at Object.require.extensions.<computed> [as .js] (/project/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:168:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:337:24)
    at async importModuleDynamicallyWrapper (node:internal/vm/module:437:15)
    at async formattedImport (/project/node_modules/mocha/lib/nodejs/esm-utils.js:9:14)
    at async Object.exports.requireOrImport (/project/node_modules/mocha/lib/nodejs/esm-utils.js:42:28)
    at async Object.exports.loadFilesAsync (/project/node_modules/mocha/lib/nodejs/esm-utils.js:100:20)
    at async singleRun (/project/node_modules/mocha/lib/cli/run-helpers.js:125:3)
    at async Object.exports.handler (/project/node_modules/mocha/lib/cli/run.js:370:5)

Anyways, for now I did a

export const env = process.env.IS_CI
	? process.env
	: createEnv({});

Thoughts?

"Type error: Type 'k' cannot be used to index type" with skipLibCheck: false

Unsure if this is a zod issue or a t3-env issue.

Check out latest commit bfa7645

pnpm install
pnpm build
pnpm build --filter=@examples/nextjs
# observe build passes

Change examples/nextjs/tsconfig.json: set "skipLibCheck": false.

pnpm build --filter=@examples/nextjs
@examples/nextjs:build: ../../packages/nextjs/dist/index.d.ts:28:371
@examples/nextjs:build: Type error: Type 'k' cannot be used to index type 'addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>'.
@examples/nextjs:build:
@examples/nextjs:build:   26 |     runtimeEnv: StrictOptions<ClientPrefix, TServer, TClient>["runtimeEnvStrict"];
@examples/nextjs:build:   27 | }
@examples/nextjs:build: > 28 | declare function createEnv<TServer extends Record<string, ZodType> = NonNullable<unknown>, TClient extends Record<`${ClientPrefix}${string}`, ZodType> = NonNullable<unknown>>({ runtimeEnv, ...opts }: Options<TServer, TClient>): (zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }> extends infer T_3 extends object ? { [k in keyof T_3]: zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>[k]; } : never) & (zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }> extends infer T_4 extends object ? { [k_1 in keyof T_4]: zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }>[k_1]; } : never) extends infer T ? { [P in keyof T]: ((zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }> extends infer T_1 extends object ? { [k in keyof T_1]: zod.objectUtil.addQuestionMarks<{ [k_1 in keyof TServer]: TServer[k_1]["_output"]; }>[k]; } : never) & (zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }> extends infer T_2 extends object ? { [k_1 in keyof T_2]: zod.objectUtil.addQuestionMarks<{ [k_2 in keyof TClient]: TClient[k_2]["_output"]; }>[k_1]; } : never))[P]; } : never;
@examples/nextjs:build:      |                                                                                                                                                                                                                                                                                                                                                                                   ^
@examples/nextjs:build:   29 |
@examples/nextjs:build:   30 | export { createEnv };
@examples/nextjs:build:   31 |
@examples/nextjs:build: โ€‰ELIFECYCLEโ€‰ Command failed with exit code 1.

Consider server only apps

At the moment properties clientPrefix and client are required in createEnv options.
These options are unnecessary for API only apps (express).
For the env-core package it would be great to make these options optional.

t3/env-core zod has no default export

Here is the issue I am seeing,


node_modules/.pnpm/@[email protected]_wnc3eutrhvkpx2b7oumqzgx5he/node_modules/@t3-oss/env-core/dist/index.d.ts:1:8 - error TS1192: Module '"<project folder>/src/sdkMetrics/src/node_modules/.pnpm/[email protected]/node_modules/zod/index"' has no default export.

1 import z, { ZodType, ZodError, ZodObject } from 'zod';
         ~

  node_modules/.pnpm/[email protected]/node_modules/zod/index.d.ts:1:1
    1 export * from "./lib";
      ~~~~~~~~~~~~~~~~~~~~~~
    'export *' does not re-export a default.


Found 1 error in node_modules/.pnpm/@[email protected]_wnc3eutrhvkpx2b7oumqzgx5he/node_modules/@t3-oss/env-core/dist/index.d.ts:1

Allow exit on failure kind of behaviour

When using t3-env on a container scenario, it will be useful to force a process.exit when invalid variables are detected.

This is due to container environments usually relying on different containers state reports to check wether it is working properly or it should be rolled back.

This can probably be implemented as a optional flag defaulted to false like exitOnFailureValidation.

The current behaviour is simply a throw via console.

โฏ docker run -p 3000:3000 t3-test-app
Listening on port 3000 url: http://e1699a2062d6:3000
โŒ Invalid environment variables: {
  DATABASE_URL: [ 'Required' ],
  NEXTAUTH_SECRET: [ 'Required' ],
  NEXTAUTH_URL: [ 'Required' ]
}
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74
Error: Invalid environment variables
    at f (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:386)
    at l (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:576)
    at C (file:///app/node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/env-nextjs/dist/index.mjs:1:749)
    at /app/.next/server/chunks/115.js:98:74```

Unsupported configuration for Nuxt - Vercel Edge

was converting my env usage from the built in nuxt one (using useRuntimeConfig) to the t3-env

ran into a a situation that is not supported

// serve/utils/kysely.ts
import { Kysely } from 'kysely'
import { PlanetScaleDialect } from 'kysely-planetscale'

import type { DB } from '~/lib/db'
import { env } from '~/env'

export const db = new Kysely<DB>({
  dialect: new PlanetScaleDialect({
    url: env.DATABASE_URL,
  }),
})

this is something that does work when using Nuxt's default runtime as such

import { Kysely } from 'kysely'
import { PlanetScaleDialect } from 'kysely-planetscale'

import type { DB } from '~/lib/db'

const config = useRuntimeConfig()
export const db = new Kysely<DB>({
  dialect: new PlanetScaleDialect({
    url: config.DATABASE_URL,
  }),
})

getting an error that crashes the edge function saying DATABASE_URL is required

this only happens when deploying to vercel-edge as opposed to just the vercel preset.

do environment variables load differently when bundled for edge? is there a way to make it use nuxt's own runtimeConfig https://nuxt.com/docs/api/composables/use-runtime-config#environment-variables

Don't require client fields

Would be nice if we're just using this for the server if we didn't need to pass in client and clientPrefix.

import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

export const env = createEnv({
    server: {
        DATABASE_URL: z.string().url(),
        OPEN_AI_API_KEY: z.string().min(1),
    },
    client: {}, // this is needed even if it's not used
    clientPrefix: 'client_',  // this is needed even if it's not used
    runtimeEnv: process.env,
});

Feature Request: `t3-env` CLI

t3-env is great for managing environment variables.
However, there's a gap when it comes to command-line operations like drizzle-kit db:push.

Currently, we use dotenv-cli to load environment variables:

$ dotenv -- drizzle-kit push:mysql

This method lacks a safety check for the loaded variables.

If we created a CLI for t3-env, it could safely load, transform, and verify these variables:

$ t3-env -- drizzle-kit push:mysql
# Loads and transforms environment variables
# Throw errors if there's an issue
# If no issues, execute the command

Breaks the checks upon the symbols combination in env variable

I'm facing the issue where it completely breaks if any of the env variables have $% combination of symbols.
In case where it is present, all other enviroment variables became undefined too.
Am I doing something wrong, or it's just a bug?
Thanks.

"next lint" fails if validation is based on NODE_ENV

As Next.js sets NODE_ENV to production when running next lint, the following example fails if the NEXTAUTH_SECRET isn't present in the local env file:

NEXTAUTH_SECRET:
  process.env.NODE_ENV === "production"
    ? z.string().min(1)
    : z.string().min(1).optional(),

One option could be changing the "lint": "next lint" to "lint": "NODE_ENV=development next lint" and that works when linting the whole project, but likely will have issues using husky and lint-staged.

Is there any recommended solution? Or should the validations be skipped completely when running next lint?

Not allowing numbers

[nodemon] restarting due to changes...
[nodemon] starting `ts-node .`
โŒ Invalid environment variables: { HTTP_PORT: [ 'Expected number, received string' ] }
import { createEnv } from '@t3-oss/env-core';
import { z } from 'zod';

export const env = createEnv({
  clientPrefix: 'PUBLIC_',
  client: {},
  server: {
    NODE_ENV: z.string().regex(/development|production/),
    ADMIN_API_KEY: z.string().min(24),
    HTTP_PORT: z.number().min(0).max(65535),
    SENTRY_DSN: z.string().url(),
  },
  runtimeEnv: {
    NODE_ENV: process.env.NODE_ENV,
    ADMIN_API_KEY: process.env.ADMIN_API_KEY,
    HTTP_PORT: process.env.HTTP_PORT,
    SENTRY_DSN: process.env.SENTRY_DSN,
  },
});

Receiving "'?' expected" at build with an older version of typescript

Repro for this: codesandbox-repro

PS: The reproduction is following the same package versions I'm using in the project I'm seeing this error

How to reproduce

  1. run npm run dev on terminal - Everything works fine, you can also check by removing from the .env and you will see that the errors will appear, so it's working as expected.
  2. run npm run build on terminal - Errors as shown on the image below

Conclusion

I can say it's a typescript version problem, I'm using 4.6.4 and it doesn't work, but the next stable version I've seen on the typescript changelog was 4.7.2 and it works fine. I don't really know what came new in this version or if it had a bug fix, but I think you should maybe say on Readme the minimum version of typescript required? I don't know if it's enough of proof, but seems like it stops breaking at 4.7.2

I've checked agains these versions of typescript:

  1. 4.7.3
  2. 4.7.2
  3. 4.6.4
  4. 4.6.3
  5. 4.6.2
  6. 4.5.5
  7. 4.5.4

Those versions before 4.7.2 seem to be broken.

image

Edge case when a variable is used by both client and server?

If i understand it right, the package assumes both the environment variables are mutually exclusive? What if I wish to use a variable in both the client and server side. For example, NEXT_PUBLIC_GOOGLE_OAUTH2_CLIENT_ID.

Currently, redefining the variable seems to be the only way to go about. Is it possible to have a shared set of values?

Getting `Unhandled Runtime Error` in nextjs

Here are my configs

// source:  /src/env.mjs
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

export const EnvironmentVariables = createEnv({
  clientPrefix: "NEXT_PUBLIC_",
  server: {},
  client: {
    NEXT_PUBLIC_API_REST_URL: z.string().url(),
    NEXT_PUBLIC_API_GRAPGQL_URL: z.string().url(),
    NEXT_PUBLIC_BOOKING_PAGE_URL: z.string().url(),
  },
  runtimeEnv: process.env, // or `import.meta.env`, or similar
});

image

How to avoid SCREAMING_SNAKE_CASE throughout codebase

I'm new to t3-env, but it's similar to hand-rolled solutions I've used in the past. The "standard" way to define a basic config file seems to be something like the following:

// config.ts

import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

const config = createEnv({
	runtimeEnv: {
		DATABASE_URL: process.env.DATABASE_URL,
		NEXT_PUBLIC_PASSWORD_MIN_LENGTH:
			process.env.NEXT_PUBLIC_PASSWORD_MIN_LENGTH
	},
	client: {
		NEXT_PUBLIC_PASSWORD_MIN_LENGTH: z
			.string()
			.transform((n) => Number.parseInt(n, 10))
			.pipe(z.number().positive())
	},
	server: { DATABASE_URL: z.string().url() }
});

export default config;

... which could be used like:

// client.ts

import config from "./config";

console.log(config.NEXT_PUBLIC_PASSWORD_MIN_LENGTH);

... or:

// server.ts

import config from "./config";

console.log(config.DATABASE_URL);

My problem with this is 1) my code is now yelling at me everywhere I use env configuration and 2) I constantly have to remember Next's client-safe prefix, even when I use the value on the server. It's forcing me to pollute my code.

What I would prefer is the following:

// config.ts

import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

const config = createEnv({
	runtimeEnv: {
		databaseUrl: process.env.DATABASE_URL,
		passwordMinLength: process.env.NEXT_PUBLIC_PASSWORD_MIN_LENGTH
	},
	client: {
		passwordMinLength: z
			.string()
			.transform((n) => Number.parseInt(n, 10))
			.pipe(z.number().positive())
	},
	server: { databaseUrl: z.string().url() }
});

export default config;

This actually seems to work fine for server-only values. I even don't get any type errors when reading the value config.passwordMinLength. Autocomplete and IDE type inspections also behave as expected. However the client config isn't happy because the client.passwordMinLength key isn't prefixed with NEXT_PUBLIC_. This type error cascades into runtimeEnv.passwordMinLength, which isn't recognized as a valid property name.

test: add some tests

probably a good idea to add some tests ๐Ÿคท๐Ÿผ

  • some type tests to make sure inference is okay, that typeerrors are thrown for invalid client variables (prefix not matching), runtimeEnvStrict should require all keys

  • some runtime tests for different environements (server/edge/client) and make sure errors are thrown when they should

createEnv returns any type.

After following the next.js implementation (within a monorepo project) the createEnv function is returning a any type.

image

Is there anything I should look for that would explain why this is happening?
Using the old env.mjs file from create-t3-app this returned type safe without issue.

feat: support more validation libraries

Instead of

createEnv({
  server: {
    KEY: z.string(),
  }
});

it should be

createEnv({
  server: z.object({
    KEY: z.string(),
  })
});

and it should not be tied to zod, meaning something like scale-ts could work for example:
import * as $ from 'scale-codec';

createEnv({
  server: $.object(
    $.field('text', $.str)
  ),
});

this would require some more considerations for typings as we'd need to pull out the shape of each to get access to the raw keys to compare against the client prefix

Should probably implement #169 first in some half-generic way ๐Ÿค”

Errors in Nuxt example

I'm trying to make this library work for Nuxt 3 application, but encountering errors:

Reproduction 1 (process.env not defined)

  1. Clone this repository
  2. cd examples/nuxt
  3. pnpm install
  4. pnpm run build
  5. pnpm run preview
  6. Open browser at localhost:3000
  7. There is an error in the console: Uncaught ReferenceError: process is not defined

The process variable is used here: https://github.com/t3-oss/t3-env/blob/b68140ae970c2626d56ed85b10be24dc909f8cab/packages/nuxt/index.ts#L22C66

Reproduction 2 (Invalid environment variables with an empty error object)

  1. Clone this repository
  2. cd examples/nuxt
  3. pnpm install
  4. pnpm run dev
  5. Open browser at localhost:3000
  6. There is an error in the console: โŒ Invalid environment variables: Object { }

The error originates from here:

((error: ZodError) => {

How to test env.mjs server side environment variables locally

I want to add tests for env.mjs, client side environments can be read, but server side will throw error, how can I mock the server side environment for testing.

env.mjs

import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
  server: {
    NODE_ENV: z.enum(["development", "test", "production"]),
  },
  client: {
    NEXT_PUBLIC_API_BASE_URL: z.string().optional(),
    NEXT_PUBLIC_API_TIMEOUT: z.coerce.number().int().positive().optional(),
  },
  runtimeEnv: {
    NODE_ENV: process.env.NODE_ENV,
    NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL,
    NEXT_PUBLIC_API_TIMEOUT: process.env.NEXT_PUBLIC_API_TIMEOUT,
  },

  skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});

env.test.ts

import { vi } from "vitest";

const processEnvs = {
  NODE_ENV: "test",
  NEXT_PUBLIC_API_BASE_URL: "http://localhost:8000",
  NEXT_PUBLIC_API_TIMEOUT: "20000",
};

describe("env.mjs", () => {
  beforeAll(() => {
    Object.entries(processEnvs).forEach(([key, value]) => {
      vi.stubEnv(key, value);
    });
  });

  afterAll(() => {
    vi.unstubAllEnvs();
  });

  it("should validate and transform server envs", async () => {
    const { env } = await import("./env.mjs");
    expect(env.NODE_ENV).toBe("test");
  });

  it("should validate and transform client envs", async () => {
    const { env } = await import("./env.mjs");
    expect(env.NEXT_PUBLIC_API_BASE_URL).toBe("http://localhost:8000");
    expect(env.NEXT_PUBLIC_API_TIMEOUT).toBe(20_000);
  });
});

Error message

 FAIL  src/env.test.ts > env.mjs > should validate and transform server envs
Error: โŒ Attempted to access a server-side environment variable on the client
 โฏ onInvalidAccess node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/core/index.ts:174:13
 โฏ Object.get node_modules/.pnpm/@[email protected][email protected][email protected]/node_modules/@t3-oss/core/index.ts:191:16
 โฏ src/env.test.ts:36:16
     34|   it("should validate and transform server envs", async () => {
     35|     const { env } = await import("./env.mjs");
     36|     expect(env.NODE_ENV).toBe("test");

raw process.env is sent to the client bundle in Astro

@t3-oss_env-core.js?v=8e23a7ad:9 Uncaught (in promise) ReferenceError: process is not defined
    at g (@t3-oss_env-core.js?v=8e23a7ad:9:30)
    at env.ts:3:20

image

image

Vite doesn't define (and doesn't remove from the code) process.env, so something else should be used, like runtimeEnv :)

maybe rename src/env.ts to src/t3Env.ts

I think instructions for Astro should include naming the file to a different name, because Astro already has src/env.d.ts file and something like src/t3Ent.ts (or t3_env, z_env, safe_env, valid_env, v_env, who knows) would be better. Then again, there's an option of trying to convince Astro to rename their file to astro.d.ts because I had so many issues with that file being named env :D

p.s. oh, right you need to convince Vite as well :) https://vitejs.dev/guide/env-and-mode.html#intellisense-for-typescript

image

image

feat: add section `build` for build-time variables

build time variables are accessible to both server and client, but is not prefixed with the client prefix, e.g. NODE_ENV, VERCEL_URL.

Should we add a new section for those?

const env = createEnv({
  build: {
    NODE_ENV: z.enum(...),
    VERCEL_URL: z.preprocess(/* do something to validate if this should exist or not */),
  },
  server: { ... },
  // ...
});

Currently, having NODE_ENV under server is the only way supported in t3-env, but the variable is actually defined on the client too, so we have to escape and use process.env.NODE_ENV throughout the codebase

Allowing boolean in runtimeEnv

I would like to use booleans without as unknown as string.

NEXT_PUBLIC_DEBUG: isDebug() as unknown as string,

import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
import {zCustomerNames} from "#/customers/customers";

export const env = createEnv({
    /*
     * Serverside Environment variables, not available on the client.
     * Will throw if you access these variables on the client.
     */
    server: {
        UPSTASH_REDIS_REST_URL: z.string().url().startsWith("https://"),
        UPSTASH_REDIS_REST_TOKEN: z.string().length(100),
        REACT_EDITOR: z.string().min(1),
    },
    /*
     * Environment variables available on the client (and server).
     *
     * ๐Ÿ’ก You'll get typeerrors if these are not prefixed with NEXT_PUBLIC_.
     */
    client: {
        NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().length(39),
        NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID: z.string().length(27),
        NEXT_PUBLIC_DEBUG: z.boolean(),
        NEXT_PUBLIC_URL: z.string().url().startsWith("https://"),
        NEXT_PUBLIC_CUSTOMER: zCustomerNames,
    },
    /*
     * Due to how Next.js bundles environment variables on Edge and Client,
     * we need to manually destructure them to make sure all are included in bundle.
     *
     * ๐Ÿ’ก You'll get typeerrors if not all variables from `server` & `client` are included here.
     */
    runtimeEnv: {
        UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
        UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
        REACT_EDITOR: process.env.REACT_EDITOR,
        NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
        NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID: process.env.NEXT_PUBLIC_GOOGLE_MAPS_PLACE_ID,
        NEXT_PUBLIC_DEBUG: isDebug() as unknown as string,
        NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL,
        NEXT_PUBLIC_CUSTOMER: process.env.NEXT_PUBLIC_CUSTOMER,
    },
});

function isDebug() {
    return (process.env.NEXT_PUBLIC_DEBUG === "true" || process.env.NEXT_PUBLIC_DEBUG === "1")
}

TS2307: Cannot find module '../../env.mjs' or its corresponding type declarations.

Getting TS2307 error. Following is my TSConfig file:

Screenshot 2023-06-01 at 12 39 15

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "checkJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "noUncheckedIndexedAccess": true
    "baseUrl": ".",
    "paths": {
      "@root/*": [
        "src/*"
      ]
    },
    "allowJs": true,
    "moduleResolution": "node"
  },
  "include": [
    "next.config.mjs",
    "next-env.d.ts",
    ".next/types/**/*.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
"exclude": ["node_modules"]
}

Package.json:

{
  "dependencies": {
    "@acme/core": "0.0.0",
    "@acme/db": "0.0.0",
    "@prisma/client": "4.15.0",
    "@t3-oss/env-nextjs": "^0.4.0",
    "@tanstack/react-query": "^4.29.12",
    "@trpc/client": "^10.28.2",
    "@trpc/next": "^10.28.2",
    "@trpc/react-query": "^10.28.2",
    "@trpc/server": "10.28.2",
    "clsx": "1.2.1",
    "daisyui": "3.0.0",
    "i18next": "22.5.0",
    "next": "13.4.4",
    "next-i18next": "13.2.2",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "7.44.2",
    "react-i18next": "^12.3.1",
    "react-query": "3.39.3",
    "superjson": "1.12.3",
    "zod": "3.21.4"
  },
  "devDependencies": {
    "@acme/tsconfig": "0.0.0",
    "@axe-core/react": "4.7.1",
    "@types/node": "20.2.5",
    "@types/react": "18.2.7",
    "@types/react-dom": "18.2.4",
    "autoprefixer": "10.4.14",
    "eslint": "8.41.0",
    "eslint-config-acme": "0.0.0",
    "eslint-plugin-tailwindcss": "3.12.1",
    "i18next-typescript": "0.1.0",
    "postcss": "8.4.24",
    "prettier": "2.8.8",
    "prettier-plugin-tailwindcss": "0.3.0",
    "tailwindcss": "3.3.2",
    "typescript": "5.0.4"
  },
}

Public prefix shouldn't be allowed on server variables

You can accidentally include one of your public environment variables in the server object, preventing you from accessing it on the client even though it is public. There should probably be an error if the server variable section has a public prefix, to prevent this from happening.

For example, this env in NextJS:

export const env = createEnv({
  server: {
    DATABASE_URL: z.string().url(),
    NEXT_PUBLIC_API_KEY: z.string().min(1),
//  ^^^^^^^^^^^^ ideally, this should be an error
  },
  client: {
    NEXT_PUBLIC_OTHER_VARIABLE: z.string().url(),
  },
  runtimeEnv: { ... },
});

Not sure how big of an issue this is but it seems like a good and fairly easy fix. I can send a PR if it would be helpful.

Display different error message if failing in NODE_ENV = test

Lots of testing frameworks (Vitest, Cypress) require a prefix when running tests for env vars. When the validation fails in a test env, the error message should be different suggesting that the user should look into if their testing framework requires a prefix for env vars to be loaded

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.