From b2d278a5383713c8399d0c6ca3eaecaa13305275 Mon Sep 17 00:00:00 2001 From: Mateusz Tkacz Date: Fri, 2 May 2025 17:06:22 +0200 Subject: [PATCH 1/2] feat: move node env flag detection to dedicated function --- README.md | 37 +++++++++++++++++++++++++++---------- src/core.test.ts | 42 +++++++++++++++++++++++++++++++++--------- src/core.ts | 24 ++++++++++++++---------- src/index.ts | 2 +- src/types.ts | 16 ++++++---------- 5 files changed, 81 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index d047765..0f48604 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Popular options include: ### Type-Safe Validation of Nested Schema ```typescript -import { parseEnv, envvar, EnvaseError } from 'envase'; +import { parseEnv, envvar } from 'envase'; import { z } from 'zod'; const config = parseEnv(process.env, { @@ -49,22 +49,24 @@ const config = parseEnv(process.env, { }, }, db: { - host: envvar('DB_HOST', z.string().min(1)), + host: envvar('DB_HOST', z.string().min(1).default('localhost')), }, - apiKey: envvar('API_KEY', z.string().min(32)), + apiKey: envvar('API_KEY', z.string().min(32).optional()), }); // config.app.listen.port -> number // config.db.host -> string -// config.apiKey -> string +// config.apiKey -> string | undefined ``` ### Environment Detection ```typescript -const config = parseEnv(process.env, {}); -// config.isProduction -> boolean -// config.isDevelopment -> boolean -// config.isTest -> boolean +import { detectNodeEnv } from 'envase'; + +const nodeEnv = detectNodeEnv(process.env); +// nodeEnv.isProduction -> boolean +// nodeEnv.isTest -> boolean +// nodeEnv.isDevelopment -> boolean ``` These flags are inferred from the `NODE_ENV` value (i.e. 'production', 'test', or 'development'). @@ -72,6 +74,9 @@ These flags are inferred from the `NODE_ENV` value (i.e. 'production', 'test', o ### Detailed error reporting ```typescript +import { parseEnv, envvar, EnvaseError } from 'envase'; +import { z } from 'zod'; + try { parseEnv(process.env, { apiKey: envvar('API_KEY', z.string().min(32)), @@ -111,6 +116,9 @@ try { ### Type Inference ```typescript +import { envvar, type InferEnv } from 'envase'; +import { z } from 'zod'; + const envSchema = { apiKey: envvar('API_KEY', z.string().min(32)), db: { @@ -135,8 +143,17 @@ This helps pair the raw env name with the shape you expect it to conform to. `parseEnv(env: Record, envSchema: T)` -Validates envvars against the schema and returns a typed configuration object -along with flags: `isProduction`, `isTest`, `isDevelopment`. +Validates envvars against the schema and returns a typed configuration object. + +### `detectNodeEnv` + +`detectNodeEnv(env: Record)` + +Standalone utility that reads NODE_ENV and returns an object with the following boolean flags: + +- isProduction: true if NODE_ENV === 'production' +- isTest: true if NODE_ENV === 'test' +- isDevelopment: true if NODE_ENV === 'development' ### `EnvaseError` diff --git a/src/core.test.ts b/src/core.test.ts index d187805..39654da 100644 --- a/src/core.test.ts +++ b/src/core.test.ts @@ -1,9 +1,41 @@ import * as v from 'valibot'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; -import { envvar, parseEnv } from './core.ts'; +import {detectNodeEnv, envvar, parseEnv} from './core.ts'; describe('core', () => { + + describe('detectNodeEnv', () => { + it('returns true for isProduction flag', () => { + const config = detectNodeEnv({ NODE_ENV: 'production' }); + + expect(config.isProduction).toBe(true); + expect(config.isTest).toBe(false); + expect(config.isDevelopment).toBe(false); + }); + it('returns true for isTest flag', () => { + const config = detectNodeEnv({ NODE_ENV: 'test' }); + + expect(config.isProduction).toBe(false); + expect(config.isTest).toBe(true); + expect(config.isDevelopment).toBe(false); + }); + it('returns true for isDevelopment flag', () => { + const config = detectNodeEnv({ NODE_ENV: 'development' }); + + expect(config.isProduction).toBe(false); + expect(config.isTest).toBe(false); + expect(config.isDevelopment).toBe(true); + }); + it('returns all falsy flags if NODE_ENV is missing', () => { + const config = detectNodeEnv({}); + + expect(config.isProduction).toBe(false); + expect(config.isTest).toBe(false); + expect(config.isDevelopment).toBe(false); + }); + }) + describe('envvar', () => { it('creates a tuple with environment variable name and Standard Schema validator', () => { const envvarName = 'API_KEY'; @@ -24,14 +56,6 @@ describe('core', () => { EMPTY: '', }; - it('sets environment flags from NODE_ENV', () => { - const config = parseEnv(mockEnv, {}); - - expect(config.isTest).toBe(true); - expect(config.isProduction).toBe(false); - expect(config.isDevelopment).toBe(false); - }); - describe('using zod', () => { it('parses flat config with Zod validators', () => { const config = parseEnv(mockEnv, { diff --git a/src/core.ts b/src/core.ts index 92813c1..bd110e1 100644 --- a/src/core.ts +++ b/src/core.ts @@ -4,9 +4,20 @@ import type { EnvSchema, EnvvarEntry, EnvvarValidationIssue, - ParseEnvOutput, + NodeEnvInfo, + InferEnv, } from './types.ts'; +export const detectNodeEnv = (env: Record): NodeEnvInfo => { + const nodeEnv = env.NODE_ENV; + + return { + isProduction: nodeEnv === 'production', + isTest: nodeEnv === 'test', + isDevelopment: nodeEnv === 'development', + }; +} + export const envvar = ( name: string, schema: T, @@ -15,7 +26,7 @@ export const envvar = ( export const parseEnv = ( env: Record, envSchema: T, -): ParseEnvOutput => { +): InferEnv => { const envvarValidationIssues: EnvvarValidationIssue[] = []; // biome-ignore lint/suspicious/noExplicitAny: Explicit 'any' is required due to nature of recursive processing @@ -61,12 +72,5 @@ export const parseEnv = ( throw new EnvaseError(envvarValidationIssues); } - const nodeEnv = env.NODE_ENV; - - return { - ...config, - isProduction: nodeEnv === 'production', - isTest: nodeEnv === 'test', - isDevelopment: nodeEnv === 'development', - }; + return config; }; diff --git a/src/index.ts b/src/index.ts index b4048c6..c7b14fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,3 @@ -export { envvar, parseEnv } from './core.ts'; +export { detectNodeEnv, envvar, parseEnv } from './core.ts'; export { EnvaseError } from './errors/envase-error.ts'; export type { InferEnv } from './types.ts'; diff --git a/src/types.ts b/src/types.ts index 81c0a6f..c812faf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,12 @@ import type { SimplifyDeep } from 'type-fest'; import type { StandardSchemaV1 } from './standard-schema.ts'; +export type NodeEnvInfo = { + isProduction: boolean; + isTest: boolean; + isDevelopment: boolean; +}; + export type EnvvarEntry = [string, T]; export type EnvSchema = { @@ -17,16 +23,6 @@ type RecursiveInferEnv = { export type InferEnv = SimplifyDeep>; -export type NodeEnvInfo = { - isProduction: boolean; - isTest: boolean; - isDevelopment: boolean; -}; - -export type ParseEnvOutput = SimplifyDeep< - RecursiveInferEnv & NodeEnvInfo ->; - export type EnvvarValidationIssue = { name: string; value?: string; From 07fdb697b649c2113a315331a600244634e7758b Mon Sep 17 00:00:00 2001 From: Mateusz Tkacz Date: Fri, 2 May 2025 17:08:01 +0200 Subject: [PATCH 2/2] chore: exec lint fix --- src/core.test.ts | 8 +++++--- src/core.ts | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core.test.ts b/src/core.test.ts index 39654da..deeec02 100644 --- a/src/core.test.ts +++ b/src/core.test.ts @@ -1,10 +1,9 @@ import * as v from 'valibot'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; -import {detectNodeEnv, envvar, parseEnv} from './core.ts'; +import { detectNodeEnv, envvar, parseEnv } from './core.ts'; describe('core', () => { - describe('detectNodeEnv', () => { it('returns true for isProduction flag', () => { const config = detectNodeEnv({ NODE_ENV: 'production' }); @@ -13,6 +12,7 @@ describe('core', () => { expect(config.isTest).toBe(false); expect(config.isDevelopment).toBe(false); }); + it('returns true for isTest flag', () => { const config = detectNodeEnv({ NODE_ENV: 'test' }); @@ -20,6 +20,7 @@ describe('core', () => { expect(config.isTest).toBe(true); expect(config.isDevelopment).toBe(false); }); + it('returns true for isDevelopment flag', () => { const config = detectNodeEnv({ NODE_ENV: 'development' }); @@ -27,6 +28,7 @@ describe('core', () => { expect(config.isTest).toBe(false); expect(config.isDevelopment).toBe(true); }); + it('returns all falsy flags if NODE_ENV is missing', () => { const config = detectNodeEnv({}); @@ -34,7 +36,7 @@ describe('core', () => { expect(config.isTest).toBe(false); expect(config.isDevelopment).toBe(false); }); - }) + }); describe('envvar', () => { it('creates a tuple with environment variable name and Standard Schema validator', () => { diff --git a/src/core.ts b/src/core.ts index bd110e1..c0af998 100644 --- a/src/core.ts +++ b/src/core.ts @@ -4,11 +4,13 @@ import type { EnvSchema, EnvvarEntry, EnvvarValidationIssue, - NodeEnvInfo, InferEnv, + NodeEnvInfo, } from './types.ts'; -export const detectNodeEnv = (env: Record): NodeEnvInfo => { +export const detectNodeEnv = ( + env: Record, +): NodeEnvInfo => { const nodeEnv = env.NODE_ENV; return { @@ -16,7 +18,7 @@ export const detectNodeEnv = (env: Record): NodeEnvI isTest: nodeEnv === 'test', isDevelopment: nodeEnv === 'development', }; -} +}; export const envvar = ( name: string,