From 9773eb37cf45b08cf0ca96cb273a0b3cb0e5145e Mon Sep 17 00:00:00 2001 From: Nan Qin Date: Wed, 29 Oct 2025 21:16:20 -0700 Subject: [PATCH] feat(utilities): add getenv --- README.md | 2 +- src/getenv.ts | 27 ++++++++++++++++++++++ src/index.ts | 1 + tests/getenv.test.ts | 54 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/getenv.ts create mode 100644 tests/getenv.test.ts diff --git a/README.md b/README.md index e5ce4c8..2b57d98 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Standard error +# DevKit ![release workflow](https://github.com/Nan0416/dev-kit/actions/workflows/release.yml/badge.svg) diff --git a/src/getenv.ts b/src/getenv.ts new file mode 100644 index 0000000..bdd96da --- /dev/null +++ b/src/getenv.ts @@ -0,0 +1,27 @@ +export function getenv(name: string, enums: ReadonlyArray, default_?: T): T; +export function getenv(name: string, default_?: string): string; + +export function getenv(name: string, v2?: ReadonlyArray | string, v3?: T): T { + const value = process.env[name]; + if (typeof value === 'string') { + if (v2 === undefined || typeof v2 === 'string') { + return value as T; + } else if (Array.isArray(v2)) { + if (v2.includes(value)) { + return value as T; + } else { + throw new Error(`Environment variable ${name}'s value ${value} is not a valid value.`); + } + } else { + throw new Error('Invalid argument.'); + } + } else { + if (typeof v2 === 'string') { + return v2 as T; + } else if (typeof v3 === 'string') { + return v3 as T; + } else { + throw new Error(`Environment variable ${name} doesn't exist.`); + } + } +} diff --git a/src/index.ts b/src/index.ts index a081f84..2134552 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,4 @@ export * from './metrics'; export * from './helper-functions'; export * from './gc'; export * from './http-client'; +export * from './getenv'; diff --git a/tests/getenv.test.ts b/tests/getenv.test.ts new file mode 100644 index 0000000..dd0218b --- /dev/null +++ b/tests/getenv.test.ts @@ -0,0 +1,54 @@ +import { getenv } from '../src'; + +describe('getenv', () => { + const ORIGINAL_ENV = process.env; + + beforeEach(() => { + process.env = { ...ORIGINAL_ENV }; // clone env + }); + + afterAll(() => { + process.env = ORIGINAL_ENV; // restore + }); + + test('returns existing string env var', () => { + process.env.FOO = 'bar'; + expect(getenv('FOO')).toBe('bar'); + }); + + test('returns existing env var when default provided', () => { + process.env.FOO = 'bar'; + expect(getenv('FOO', 'default')).toBe('bar'); + }); + + test('returns default when env var missing', () => { + expect(getenv('FOO', 'default')).toBe('default'); + }); + + test('throws error when missing and no default provided', () => { + expect(() => getenv('FOO')).toThrow("Environment variable FOO doesn't exist."); + }); + + test('returns value if included in allowed enum list', () => { + process.env.MODE = 'prod'; + const allowed = ['dev', 'prod'] as const; + expect(getenv('MODE', allowed)).toBe('prod'); + }); + + test('throws error if value not in allowed enum list', () => { + process.env.MODE = 'staging'; + const allowed = ['dev', 'prod'] as const; + expect(() => getenv('MODE', allowed)).toThrow("Environment variable MODE's value staging is not a valid value."); + }); + + test('returns default enum if env missing', () => { + const allowed = ['dev', 'prod'] as const; + expect(getenv('MODE', allowed, 'prod')).toBe('prod'); + }); + + test('throws if invalid overload argument type encountered', () => { + process.env.FOO = 'bar'; + // @ts-expect-error invalid argument type + expect(() => getenv('FOO', 123)).toThrow('Invalid argument.'); + }); +});