genenv-cli
v2.0.2
Published
Generate typed environment configs from your .env file
Maintainers
Readme
genenv
Generate typed, framework-aware environment configs from a .env file — straight from the command line.
[!IMPORTANT] Package renamed:
genenv-configis nowgenenv-cli. If you previously installedgenenv-config, uninstall and reinstall:npm uninstall -g genenv-config npm install -g genenv-cliSince v2.0.2: this rename notice still applies — the package name and
genenvcommand introduced with the rename are unchanged in 2.0.2. Replace this line with your actual 2.0.1 → 2.0.2 changelog notes if there are behavior changes to call out.
Table of Contents
- Project Overview
- Why Use This CLI?
- Pros and Cons
- Installation
- Quick Start
- CLI Commands
- CLI Flags and Options
- Command Examples
- Sample Generated Output
- Input Parsing Rules
- Common Use Cases
- Automation and Scripting
- Error Handling
- Troubleshooting
- Best Practices
- Building From Source
- FAQ
- License
1. Project Overview
genenv is a CLI tool that reads a .env file and generates a ready-to-import environment module (env.ts, optionally env.js, and optionally .env.example) shaped for a specific JavaScript runtime or framework — Node, Express, Next.js, Remix, Vite, Astro, Bun, or Deno.
It parses your .env file into typed key/value entries, inferring string, number, or boolean for each value, then writes a generated file that reads those keys from the correct runtime API (process.env, import.meta.env, or Deno.env) with appropriate type casting. Optionally, it generates a Zod-validated schema instead of a plain cast, so invalid or missing variables fail fast at startup.
Who it's for: developers who want type-safe, IDE-friendly access to environment variables without hand-writing and maintaining that boilerplate every time .env changes.
Main workflow:
- Keep
.envas the source of truth for your environment variables. - Run
genenv init(first time) orgenenv generate(subsequent runs) to regenerate the env module. - Import the generated file from your application code.
CLI-only tool.
genenvis not designed to be imported as a JavaScript/TypeScript library — attempting torequire/importit programmatically causes it to print an error and exit.
Don't want to install anything? You can run every command in this README through
npxinstead of a global install — see Run without installing (npx).
2. Why Use This CLI?
- Removes hand-written boilerplate — no more manually writing
Number(process.env.PORT)casts and null checks for every variable. - Framework-aware output — generates the correct access pattern (
process.env,import.meta.env, orDeno.env.get()) for 8 different targets. - Type inference — automatically infers
string,number, orbooleanper variable from the.envfile, no manual annotation needed. - Optional runtime validation — the
--zodflag generates a schema that throws a clear error if a required variable is missing or malformed. - Optional
.env.example— pass--exampleto generate a safe-to-commit template alongside your typed module. - Scriptable — both commands are non-interactive and exit with a non-zero code on failure, making them usable in
npmscripts, pre-build steps, or CI. - Zero-config onboarding —
genenv initdetects your framework frompackage.jsonand installsdotenvautomatically when needed. - No global install required — run it on demand with
npxif you'd rather not add a global binary.
When it may not be the right choice: if you don't want a generated source file checked into your project, need to merge multiple .env files in one run, need config formats beyond simple KEY=value pairs, or need scientific-notation numeric parsing.
3. Pros and Cons
Pros
- Zero-config
initmode with automatic framework detection anddotenvinstallation. - Framework-specific env access code for 8 targets (Node, Express, Next.js, Remix, Vite, Astro, Bun, Deno).
- Automatic type inference (
string/number/boolean) from.envvalues. - Optional Zod-based runtime validation with clear failure messages.
- Output directory is created automatically if it doesn't exist.
- Colorized, readable console output summarizing what was parsed and generated.
- No required runtime dependency for the default (non-Zod) output.
- Works via
npxwith no global install.
Cons
- No persistent configuration file — every run relies entirely on CLI flags.
- Only a single
.envfile can be parsed per run (no multi-file merging). - Number inference does not support scientific notation (
1e3is treated as a string). - The generated
env.jsmixes a"use strict"header with an ES moduleexport defaultstatement — confirm it matches your project's module system before using it. - No
--versionflag; the version string is only visible inside--helpoutput. - Running
genenvwith no arguments (or a bare word likegenenv next) only prints help — it does not runinit. You must rungenenv initorgenenv generateexplicitly. - All output, including errors, is written to stdout rather than stderr.
- Minimum Node.js version and supported operating systems are not confirmed — Needs verification.
4. Installation
Prerequisites
- Node.js (minimum version — Needs verification).
- A package manager: npm, yarn, pnpm, or bun.
Package name vs. command name: the npm package is published as
genenv-cli, but installing it puts agenenvcommand on your PATH. Install using the package name; run using the command name.
Global install
# npm
npm install -g genenv-cli
# yarn (needs verification)
yarn global add genenv-cli
# pnpm (needs verification)
pnpm add -g genenv-cliRun without installing (npx)
If you don't want a global install — for example, in a one-off script or a CI job you don't want to maintain — run it directly with npx.
The npm package is named genenv-cli, but its package.json only declares one entry in bin:
"bin": {
"genenv": "dist/cli.js"
}Because there's exactly one bin script, npx resolves it automatically — you don't need to spell out genenv a second time or pass -p/--package:
npx genenv-cli init
npx genenv-cli generate --next --zod
npx genenv-cli --helpThis works even if genenv has never been installed globally or locally — npx fetches genenv-cli on demand for that single run, sees it has one bin, and runs genenv from it.
Already have it installed? Once genenv-cli is a local dependency (after npm install), installed globally, or linked with npm link, the genenv binary already exists on your machine — so you can drop -cli entirely and just run:
genenv init
genenv generate --next --zod-cli only needs to appear the one time you tell npx which package to fetch. After that, every command you actually type is just genenv.
Verifying the installation
There is no --version flag. Verify by running:
genenv --help
# or, without a global install:
npx genenv-cli --helpThis prints the version string as part of the help output.
5. Quick Start
With a global install:
npm install -g genenv-cli
genenv initWith npx (no install):
npx genenv-cli initEither way, init reads .env from the current directory, detects your framework from package.json (falling back to Node.js), installs dotenv automatically if needed, and writes env.ts to src/lib/. Pass --example to also generate .env.example in the same output directory.
6. CLI Commands
init
Zero-config, opinionated setup — the recommended starting point for a new project.
- Default output directory:
src/lib/ - Framework auto-detection: checks
package.jsondependencies in order —next→remix/@remix-run/node→astro→vite→express— falling back tonode. dotenvauto-install: runs automatically for Node, Express, and Remix targets (not needed for Next, Vite, Astro, Bun, or Deno).
genenv init
genenv init --next --zod
genenv init --express --zod --examplegenerate
Explicit, non-opinionated generation with full flag control. Does not auto-detect the framework and does not auto-install dotenv. Best for CI and automation.
- Default output directory:
.(current directory)
genenv generate
genenv generate --remix --zod
genenv generate --vite -i apps/web/.env -o apps/web/src/libNote: running
genenvwith no command, or with an unrecognized bare word likegenenv next, only prints help. See Troubleshooting.
7. CLI Flags and Options
| Flag | Short | Default | Description |
| ------------- | ------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --input | -i | .env | Path to the source.env file to parse. |
| --output | -o | . (generate) / src/lib (init) | Directory the generated files are written into. Created automatically if missing. When omitted, files are written to the project root (forgenerate) or src/lib/ (for init). |
| --name | -n | env | Base file name (without extension) for the generated env module.--name appEnv → appEnv.ts / appEnv.js. |
| --zod | — | false | Generates a Zod-validatedenv.ts instead of a plain typed one. Requires zod to be installed. |
| --js | — | false | Also emitsenv.js alongside env.ts. |
| --example | — | false | Generates.env.example in the same output directory as env.ts. Opt-in — off by default. |
| --node | — | (auto) | Target Node.js (process.env). |
| --express | — | — | Target Express (process.env). |
| --vite | — | — | Target Vite (import.meta.env). |
| --next | — | — | Target Next.js (process.env; Next loads .env natively). |
| --remix | — | — | Target Remix (server-onlyprocess.env). |
| --astro | — | — | Target Astro (import.meta.env). |
| --bun | — | — | Target Bun (process.env; Bun auto-loads .env). |
| --deno | — | — | Target Deno (Deno.env.get()). |
| --help | -h | — | Prints usage and exits. |
Only one framework flag may be used per run — passing two throws: Multiple frameworks specified: ... Pick one.
Output file locations
| File | Default location | With-o src/lib |
| ---------------- | ------------------------------------------------------ | ------------------------ |
| env.ts | Project root (generate) or src/lib/ (init) | src/lib/env.ts |
| env.js | Same asenv.ts | src/lib/env.js |
| .env.example | Same directory asenv.ts | src/lib/.env.example |
.env.examplealways lands in the same directory asenv.ts— wherever that is. It is not hardcoded to the project root.
8. Command Examples
# Zero-config setup (auto-detects framework)
genenv init
# Init for Next.js with Zod validation
genenv init --next --zod
# Generate for Node (default), output to current directory
genenv generate
# Generate for Remix with Zod validation
genenv generate --remix --zod
# Generate for Vite with custom input and output paths
genenv generate --vite -i apps/web/.env -o apps/web/src/lib
# Generate for Deno into a specific output directory
genenv generate --deno -o src/lib
# Also emit env.js alongside env.ts
genenv generate --bun --js
# Generate with .env.example in the same output directory
genenv generate --example
# Custom output file base name → appEnv.ts
genenv generate --name appEnv
# Show help and version string
genenv --helpNot installed? Swap
genenvfornpx genenv-cliin any of the above — see Run without installing (npx).
9. Sample Generated Output
Given this .env file:
PORT=3000
DATABASE_URL=postgres://localhost/myapp
DEBUG=trueRunning genenv generate --node produces env.ts:
import "dotenv/config";
function requireString(key: string): string {
const val = process.env[key];
if (val === undefined || val === "") throw new Error(`Missing env var: ${key}`);
return val;
}
function requireNumber(key: string): number {
const val = process.env[key];
if (val === undefined || val === "") throw new Error(`Missing env var: ${key}`);
const n = Number(val);
if (isNaN(n)) throw new Error(`Env var ${key} is not a valid number: "${val}"`);
return n;
}
function requireBoolean(key: string): boolean {
const val = (process.env[key] ?? "").toLowerCase();
return val === "true";
}
const env = {
PORT: requireNumber("PORT"),
DATABASE_URL: requireString("DATABASE_URL"),
DEBUG: requireBoolean("DEBUG"),
};
export default env;Running genenv generate --node --zod instead produces a Zod-validated env.ts:
import "dotenv/config";
import { z } from "zod";
export const EnvSchema = z.object({
PORT: z.coerce.number(),
DATABASE_URL: z.string().min(1),
DEBUG: z.enum(["true", "false"]).transform((v) => v === "true"),
});
const env = EnvSchema.parse(process.env);
export default env;Running with --vite swaps process.env for import.meta.env throughout, and omits the dotenv import (Vite loads .env natively). Running with --deno uses Deno.env.get(key) instead.
10. Input Parsing Rules
Source file: .env by default; override with -i/--input.
Line parsing:
- Blank lines and lines starting with
#are skipped. - Lines without
=are skipped. - Inline comments are stripped only when preceded by a space —
VALUE # commentstrips the comment, butVALUE#commentkeeps the#as part of the value. - A single matching pair of surrounding
"or'quotes is stripped from the value. - Duplicate keys: the last value wins, but the key keeps the position where it was first seen.
Type inference:
| Type | Detected when |
| ----------- | ------------------------------------------------------------------------------------ |
| boolean | Value istrue or false (case-insensitive) |
| number | Integer, negative, or decimal:42, -3.14, .5, Infinity, -Infinity |
| string | Anything else, including empty values |
Scientific notation (
1e3) and trailing-dot decimals (5.) are not recognized as numbers and are treated as strings.
Framework env access in generated code:
| Flag | Access pattern | dotenv auto-installed by init? |
| -------------------- | ------------------- | -------------------------------------- |
| --node (default) | process.env | Yes |
| --express | process.env | Yes |
| --remix | process.env | Yes |
| --next | process.env | No — Next loads.env natively |
| --vite | import.meta.env | No — Vite loads.env natively |
| --astro | import.meta.env | No — Astro loads.env natively |
| --bun | process.env | No — Bun auto-loads.env |
| --deno | Deno.env.get() | No — built in |
11. Common Use Cases
Bootstrapping a new Next.js project
genenv initAuto-detects Next.js from package.json, skips the dotenv install, and writes env.ts to src/lib/.
Adding fail-fast validation to an Express API
genenv generate --express --zodRequires zod to be installed. Generates a schema that throws at startup if a required variable is missing or malformed.
Generating Vite-safe env types for a frontend app
genenv generate --vite -o src/libUses import.meta.env instead of process.env, matching Vite's client-side env handling.
Keeping .env.example in sync as .env changes
genenv generate --exampleRegenerates both env.ts and .env.example in the same directory. Pass --example each time — it's opt-in.
Multiple env modules in one project
genenv generate --name serverEnv -i .env.server
genenv generate --name clientEnv -i .env.client --viteUsing -n/--name prevents one run from overwriting the other's output.
One-off use without installing anything
npx genenv-cli generate --exampleHandy for a quick check on a machine where you don't want to add a global binary.
12. Automation and Scripting
Both commands are non-interactive and exit with code 1 on failure, making them safe for npm scripts, pre-build hooks, and CI:
{
"scripts": {
"env:generate": "genenv generate --zod --example",
"prebuild": "genenv generate"
}
}If you'd rather not add genenv-cli as a dependency at all, call it through npx instead:
{
"scripts": {
"env:generate": "npx genenv-cli generate --zod --example",
"prebuild": "npx genenv-cli generate"
}
}In CI, prefer genenv generate over init — it skips auto-detection and auto-install, giving deterministic, dependency-safe behavior. Check the exit code to fail the build on a missing or invalid .env; do not parse stderr, since all output goes to stdout.
13. Error Handling
| Situation | Message | Exit code |
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------- |
| Two framework flags passed | Multiple frameworks specified: <flagA> and <flagB>. Pick one. | 1 |
| -i, -o, or -n given with no following value | Missing value for <flag> <path/name> | 1 |
| Unrecognized flag (starts with-) | Unknown flag: <flag>. Run genenv --help to see available options. | 1 |
| Input.env file not found | File not found: <path> | 1 |
| --zod used without zod installed | Cannot generate a Zod schema: the "zod" package is not installed… + npm install zod | 1 |
| Filesystem error (permissions, disk full, etc.) | Propagated from Node'sfs module | 1 |
A bare word that isn't init, generate, or a recognized --flag is silently ignored rather than producing an error.
Exit codes: 0 on success or when --help is shown; 1 on any error.
14. Troubleshooting
genenv with no arguments only prints help, even though the help text implies it runs init.
The CLI requires an explicit command. Run genenv init explicitly.
genenv next does nothing and prints help.
Framework names are only recognized as --flags. Use genenv init --next or genenv generate --next.
File not found: .env
No .env file exists at the default path or the path given via -i. Create the file or pass the correct path: genenv generate -i path/to/.env.
Cannot generate a Zod schema: the "zod" package is not installed…
Run npm install zod, then re-run your genenv command.
dotenv wasn't installed automatically.
Automatic installation only happens with genenv init, not genenv generate, and only for Node, Express, and Remix targets. Run genenv init, or install dotenv manually.
The generated env.js fails to load in a CommonJS context.
env.js is written with a "use strict" header but ends with export default (ES module syntax). Ensure your project uses ES modules, or adjust the export before importing.
.env.example didn't appear — or appeared in the wrong place.
--example is opt-in and must be passed explicitly. When it is passed, .env.example is written to the same directory as env.ts (controlled by -o/--output). If you didn't pass -o, that's the project root for generate or src/lib/ for init.
npm install -g genenv fails or installs the wrong package.
The npm package name is genenv-cli. Install with npm install -g genenv-cli; the genenv command becomes available afterward. If you don't want a global install at all, use npx genenv-cli <command> instead.
15. Best Practices
- Commit
.env.example, never.env. - Re-run
genenv generatewhenever.envchanges — hook it toprebuildor a CI step. - Prefer
--zodfor services where a missing variable should fail fast at startup rather than surface later as a runtime bug. - Pass the framework flag that matches your actual runtime. Using
--vitein a Node server generatesimport.meta.envaccess, which doesn't exist there. - Use
genenv initonly for first-time setup; usegenenv generatefor repeatable CI runs. - Use
-n/--namewhen generating more than one env module in the same project. - In scripts, check the process exit code — don't parse stderr, because all output including errors goes to stdout.
- When documenting installation for others, always pair the package name (
genenv-cli) with the command name (genenv) to avoid confusion. - If contributors shouldn't need a global install, prefer
npx genenv-cli ...in scripts and docs over assuminggenenvis on everyone's PATH.
16. FAQ
What does this CLI do?
It parses a .env file and generates a typed, framework-specific module (env.ts, optionally env.js) and an optional .env.example, eliminating hand-written environment variable access and casting.
How do I install it?
npm install -g genenv-cli. The npm package is genenv-cli; the CLI command it installs is genenv. Prefer not to install anything? Use npx genenv-cli <command> instead.
How do I check which version I have?
Run genenv --help — the version string appears in the help output. There is no standalone --version flag.
Does .env.example get generated automatically?
No — pass --example to generate it. It is written to the same directory as env.ts.
Can I change the generated file's name?
Yes — pass -n/--name <name>. Default is env → env.ts/env.js.
What Node.js versions are supported? Not specified in the project files — needs verification.
Does it work on Windows, macOS, and Linux? Not confirmed — needs verification.
Does it support JavaScript output?
Yes — pass --js to also generate a CommonJS-style env.js alongside env.ts. Note the module system caveat in Troubleshooting.
How do I configure it?
Entirely through CLI flags and your .env file. There is no config file.
How do I see available commands and flags?
genenv --help (or -h), or npx genenv-cli --help if you haven't installed it.
How do I build the CLI itself from source?
See Building From Source — clone the repo, npm install, then npm run build.
What are the main limitations?
No config file, single-file .env input only, no scientific-notation number parsing, no --version flag, and bare-word commands are silently ignored rather than producing an error.
17. License
MIT License
Copyright (c) genenv contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
