Environment
Read the environment envapt detects from ENVIRONMENT, ENV, NODE_ENV, or MODE, and pin it yourself when a script or a test needs a fixed one.
You've written process.env.NODE_ENV === 'production' before. It reads one variable and compares one exact spelling, so PRODUCTION takes the other branch and logs nothing. It also finds nothing if NODE_ENV isn't set, and a Vite build exposes MODE instead.
envapt checks several variables, ignores case, and resolves one of the environments in the table below. Detection works on every runtime once you've bound a source. On Node, Bun, and Deno the environment also determines which .env files load, as .env files on Node shows.
Detecting the environment
envapt reads these variables in order and uses the first one that has a value:
ENVIRONMENT → ENV → NODE_ENV → MODE → developmentThe first variable with a value decides, even when its value is unrecognized. With ENV=prod and NODE_ENV=production both set, the result is 'development', because ENV comes first and prod matches no environment. Set Envapter.debug = 'warn' to log that fallback (see Errors and debugging).
An empty value counts as unset, so envapt checks the next variable. A whitespace-only value counts as unset only in strict mode. ENVIRONMENT_KEYS, exported from envapt, is a readonly tuple of these variable names in this order, for tools that need to read the same variables.
envapt detects the environment on the first read and keeps the result until the cache rebuilds or you pin one. A test that sets process.env.NODE_ENV after something has read Envapter.environment still reads the old value until then.
envapt matches the value it found, ignoring case, against these environments:
| Variable holds | Envapter.environment |
|---|---|
production or PRODUCTION | 'production' |
staging | 'staging' |
test | 'test' |
development | 'development' |
anything else, such as prod | 'development' |
| no variable set | 'development' |
The variables envapt reads come from the bound source. On Node that is process.env plus your .env files. envapt picks which .env files to load before it reads any of them, so a NODE_ENV line inside .env doesn't change the files (see the warning on .env files on Node).
In a Vite build, PortableSource(import.meta.env) makes MODE available. envapt checks MODE last, so any variable earlier in that order wins over it (Browser has the setup).
If you read the environment on the portable build before binding a source, envapt throws NoSourceBound (307).
Branching on the environment
Envapter.isProduction, isStaging, isDevelopment, and isTest are booleans for the common checks.
const logLevel = . ? 'warn' : 'debug';logLevel is the union 'warn' | 'debug'. When each environment needs its own setting, type a record with Environment as its keys, one for each environment in the table above. If you omit one, TypeScript reports an error.
const : <, 'debug' | 'info' | 'warn' | 'silent'> = {
: 'debug',
: 'info',
: 'warn',
: 'silent'
};
export const = [.];Pinning the environment
A script that takes its mode from a flag can't rely on detection. A test that must always run as 'test' can't rely on it either, whatever the shell sets. Assign Envapter.environment to pin it.
. = .;A string such as 'production' works too, matched ignoring case. If you assign a name envapt doesn't recognize, the environment becomes 'development'. If you assign a value that isn't a string, envapt throws InvalidUserDefinedConfig (302).
Assigning Envapter.environment rebuilds the cache, so on Node the .env files for the new environment load when you assign. After import 'envapt/config', the first load has already written keys into process.env, and those keys win over the files the new environment loads (Coming from dotenv). Pinning changes nothing for them. A pin survives later cache rebuilds. Without a pin, a rebuild detects the environment again.
Pin it once, in a module your entry file imports before anything that reads config. Imports run before your module body, so a pin in the entry file's body runs after every imported module has loaded. If one of those modules reads the environment while it loads, it gets the old value. On the portable build you can pin before useSource, and the bind applies the pin.
On Node, Envapter.resetPaths() is the only way to remove a pin, and it also removes any envPaths and profiles, the extra file paths you set per environment. On the portable build resetPaths() warns and does nothing, so a pin there changes only when you assign again.