Skip to content

Commit 96055a8

Browse files
authored
feat: move node env flag detection to dedicated function (#5)
* feat: move node env flag detection to a dedicated function
1 parent fdac232 commit 96055a8

File tree

5 files changed

+85
-40
lines changed

5 files changed

+85
-40
lines changed

README.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Popular options include:
3939
### Type-Safe Validation of Nested Schema
4040

4141
```typescript
42-
import { parseEnv, envvar, EnvaseError } from 'envase';
42+
import { parseEnv, envvar } from 'envase';
4343
import { z } from 'zod';
4444

4545
const config = parseEnv(process.env, {
@@ -49,29 +49,34 @@ const config = parseEnv(process.env, {
4949
},
5050
},
5151
db: {
52-
host: envvar('DB_HOST', z.string().min(1)),
52+
host: envvar('DB_HOST', z.string().min(1).default('localhost')),
5353
},
54-
apiKey: envvar('API_KEY', z.string().min(32)),
54+
apiKey: envvar('API_KEY', z.string().min(32).optional()),
5555
});
5656
// config.app.listen.port -> number
5757
// config.db.host -> string
58-
// config.apiKey -> string
58+
// config.apiKey -> string | undefined
5959
```
6060

6161
### Environment Detection
6262

6363
```typescript
64-
const config = parseEnv(process.env, {});
65-
// config.isProduction -> boolean
66-
// config.isDevelopment -> boolean
67-
// config.isTest -> boolean
64+
import { detectNodeEnv } from 'envase';
65+
66+
const nodeEnv = detectNodeEnv(process.env);
67+
// nodeEnv.isProduction -> boolean
68+
// nodeEnv.isTest -> boolean
69+
// nodeEnv.isDevelopment -> boolean
6870
```
6971

7072
These flags are inferred from the `NODE_ENV` value (i.e. 'production', 'test', or 'development').
7173

7274
### Detailed error reporting
7375

7476
```typescript
77+
import { parseEnv, envvar, EnvaseError } from 'envase';
78+
import { z } from 'zod';
79+
7580
try {
7681
parseEnv(process.env, {
7782
apiKey: envvar('API_KEY', z.string().min(32)),
@@ -111,6 +116,9 @@ try {
111116
### Type Inference
112117

113118
```typescript
119+
import { envvar, type InferEnv } from 'envase';
120+
import { z } from 'zod';
121+
114122
const envSchema = {
115123
apiKey: envvar('API_KEY', z.string().min(32)),
116124
db: {
@@ -135,8 +143,17 @@ This helps pair the raw env name with the shape you expect it to conform to.
135143

136144
`parseEnv(env: Record<string, string | undefined>, envSchema: T)`
137145

138-
Validates envvars against the schema and returns a typed configuration object
139-
along with flags: `isProduction`, `isTest`, `isDevelopment`.
146+
Validates envvars against the schema and returns a typed configuration object.
147+
148+
### `detectNodeEnv`
149+
150+
`detectNodeEnv(env: Record<string, string | undefined>)`
151+
152+
Standalone utility that reads NODE_ENV and returns an object with the following boolean flags:
153+
154+
- isProduction: true if NODE_ENV === 'production'
155+
- isTest: true if NODE_ENV === 'test'
156+
- isDevelopment: true if NODE_ENV === 'development'
140157

141158
### `EnvaseError`
142159

src/core.test.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,43 @@
11
import * as v from 'valibot';
22
import { describe, expect, it } from 'vitest';
33
import { z } from 'zod';
4-
import { envvar, parseEnv } from './core.ts';
4+
import { detectNodeEnv, envvar, parseEnv } from './core.ts';
55

66
describe('core', () => {
7+
describe('detectNodeEnv', () => {
8+
it('returns true for isProduction flag', () => {
9+
const config = detectNodeEnv({ NODE_ENV: 'production' });
10+
11+
expect(config.isProduction).toBe(true);
12+
expect(config.isTest).toBe(false);
13+
expect(config.isDevelopment).toBe(false);
14+
});
15+
16+
it('returns true for isTest flag', () => {
17+
const config = detectNodeEnv({ NODE_ENV: 'test' });
18+
19+
expect(config.isProduction).toBe(false);
20+
expect(config.isTest).toBe(true);
21+
expect(config.isDevelopment).toBe(false);
22+
});
23+
24+
it('returns true for isDevelopment flag', () => {
25+
const config = detectNodeEnv({ NODE_ENV: 'development' });
26+
27+
expect(config.isProduction).toBe(false);
28+
expect(config.isTest).toBe(false);
29+
expect(config.isDevelopment).toBe(true);
30+
});
31+
32+
it('returns all falsy flags if NODE_ENV is missing', () => {
33+
const config = detectNodeEnv({});
34+
35+
expect(config.isProduction).toBe(false);
36+
expect(config.isTest).toBe(false);
37+
expect(config.isDevelopment).toBe(false);
38+
});
39+
});
40+
741
describe('envvar', () => {
842
it('creates a tuple with environment variable name and Standard Schema validator', () => {
943
const envvarName = 'API_KEY';
@@ -24,14 +58,6 @@ describe('core', () => {
2458
EMPTY: '',
2559
};
2660

27-
it('sets environment flags from NODE_ENV', () => {
28-
const config = parseEnv(mockEnv, {});
29-
30-
expect(config.isTest).toBe(true);
31-
expect(config.isProduction).toBe(false);
32-
expect(config.isDevelopment).toBe(false);
33-
});
34-
3561
describe('using zod', () => {
3662
it('parses flat config with Zod validators', () => {
3763
const config = parseEnv(mockEnv, {

src/core.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@ import type {
44
EnvSchema,
55
EnvvarEntry,
66
EnvvarValidationIssue,
7-
ParseEnvOutput,
7+
InferEnv,
8+
NodeEnvInfo,
89
} from './types.ts';
910

11+
export const detectNodeEnv = (
12+
env: Record<string, string | undefined>,
13+
): NodeEnvInfo => {
14+
const nodeEnv = env.NODE_ENV;
15+
16+
return {
17+
isProduction: nodeEnv === 'production',
18+
isTest: nodeEnv === 'test',
19+
isDevelopment: nodeEnv === 'development',
20+
};
21+
};
22+
1023
export const envvar = <T extends StandardSchemaV1>(
1124
name: string,
1225
schema: T,
@@ -15,7 +28,7 @@ export const envvar = <T extends StandardSchemaV1>(
1528
export const parseEnv = <T extends EnvSchema>(
1629
env: Record<string, string | undefined>,
1730
envSchema: T,
18-
): ParseEnvOutput<T> => {
31+
): InferEnv<T> => {
1932
const envvarValidationIssues: EnvvarValidationIssue[] = [];
2033

2134
// biome-ignore lint/suspicious/noExplicitAny: Explicit 'any' is required due to nature of recursive processing
@@ -61,12 +74,5 @@ export const parseEnv = <T extends EnvSchema>(
6174
throw new EnvaseError(envvarValidationIssues);
6275
}
6376

64-
const nodeEnv = env.NODE_ENV;
65-
66-
return {
67-
...config,
68-
isProduction: nodeEnv === 'production',
69-
isTest: nodeEnv === 'test',
70-
isDevelopment: nodeEnv === 'development',
71-
};
77+
return config;
7278
};

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { envvar, parseEnv } from './core.ts';
1+
export { detectNodeEnv, envvar, parseEnv } from './core.ts';
22
export { EnvaseError } from './errors/envase-error.ts';
33
export type { InferEnv } from './types.ts';

src/types.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import type { SimplifyDeep } from 'type-fest';
22
import type { StandardSchemaV1 } from './standard-schema.ts';
33

4+
export type NodeEnvInfo = {
5+
isProduction: boolean;
6+
isTest: boolean;
7+
isDevelopment: boolean;
8+
};
9+
410
export type EnvvarEntry<T extends StandardSchemaV1> = [string, T];
511

612
export type EnvSchema = {
@@ -17,16 +23,6 @@ type RecursiveInferEnv<T extends EnvSchema> = {
1723

1824
export type InferEnv<T extends EnvSchema> = SimplifyDeep<RecursiveInferEnv<T>>;
1925

20-
export type NodeEnvInfo = {
21-
isProduction: boolean;
22-
isTest: boolean;
23-
isDevelopment: boolean;
24-
};
25-
26-
export type ParseEnvOutput<T extends EnvSchema> = SimplifyDeep<
27-
RecursiveInferEnv<T> & NodeEnvInfo
28-
>;
29-
3026
export type EnvvarValidationIssue = {
3127
name: string;
3228
value?: string;

0 commit comments

Comments
 (0)