env-verifier
Advanced tools
Comparing version 1.0.2 to 1.1.2
export interface MappedConfig { | ||
[key: string]: string | undefined | MappedConfig; | ||
[key: string]: any | string | undefined | MappedConfig; | ||
} | ||
interface ConfigWithEnvKeys { | ||
[key: string]: string | ConfigWithEnvKeys; | ||
export interface TransformFn { | ||
(envValue: string): any; | ||
} | ||
export declare type TransformTuple = [string, TransformFn]; | ||
export interface ConfigWithEnvKeys { | ||
[key: string]: string | InsertValue | TransformTuple | ConfigWithEnvKeys; | ||
} | ||
export interface VerifiedConfig { | ||
[key: string]: string | VerifiedConfig; | ||
[key: string]: any | string | VerifiedConfig; | ||
} | ||
declare class InsertValue { | ||
value: any; | ||
constructor(value: any); | ||
} | ||
export declare function verify(config: ConfigWithEnvKeys, env?: { | ||
@@ -19,2 +27,3 @@ [key: string]: string | undefined; | ||
}): VerifiedConfig; | ||
export declare function insert(value: any): InsertValue; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
class InsertValue { | ||
constructor(value) { | ||
this.value = value; | ||
} | ||
} | ||
const recursiveVerify = ({ config, env, errors = [], path = '' }) => { | ||
@@ -7,7 +12,21 @@ const mapConf = (key) => { | ||
const subPath = path.length === 0 ? key : `${path}.${key}`; | ||
if (typeof value === 'string') { | ||
const envValue = env[value]; | ||
const getValueFromEnv = (key) => { | ||
const envValue = env[key]; | ||
if (envValue === undefined || envValue.length === 0) { | ||
errors.push(new Error(`environment value ${value} is missing from config object at ${subPath}`)); | ||
errors.push(new Error(`environment value ${key} is missing from config object at ${subPath}`)); | ||
return undefined; | ||
} | ||
return envValue; | ||
}; | ||
if (value instanceof InsertValue) { | ||
return { [key]: value.value }; | ||
} | ||
else if (Array.isArray(value)) { | ||
const [envKey, transformFn] = value; | ||
const envValue = getValueFromEnv(envKey); | ||
const transforedValue = envValue && transformFn(envValue); | ||
return { [key]: transforedValue }; | ||
} | ||
else if (typeof value === 'string') { | ||
const envValue = getValueFromEnv(value); | ||
return { [key]: envValue }; | ||
@@ -47,2 +66,6 @@ } | ||
exports.strictVerify = strictVerify; | ||
function insert(value) { | ||
return new InsertValue(value); | ||
} | ||
exports.insert = insert; | ||
//# sourceMappingURL=index.js.map |
@@ -52,19 +52,64 @@ "use strict"; | ||
const env = { | ||
a: 'A' | ||
PRESENT: 'present' | ||
}; | ||
const config = { | ||
1: 'a', | ||
2: 'zz-top', | ||
1: 'PRESENT', | ||
2: 'MISSING', | ||
3: { | ||
6: { | ||
7: 'IM_ALSO_MISSING' | ||
7: 'MISSING' | ||
}, | ||
4: 'IM_MISSING', | ||
5: 'ME_TOO' | ||
4: 'MISSING', | ||
5: 'MISSING' | ||
} | ||
}; | ||
expect(index_1.verify(config, env).errors.length).toEqual(4); | ||
const result = index_1.verify(config, env).errors; | ||
expect(result.length).toEqual(4); | ||
}); | ||
}); | ||
}); | ||
describe('with transform functions', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
}; | ||
it('allows a tuple with a string and transform function', () => { | ||
const configObj = { | ||
present: ['PRESENT', (envVal) => envVal] | ||
}; | ||
expect(() => index_1.verify(configObj, env)).not.toThrow(); | ||
}); | ||
it('allows the same tuple in a nested object', () => { | ||
const configObj = { | ||
nested: { | ||
present: ['PRESENT', (envVal) => envVal] | ||
} | ||
}; | ||
const result = index_1.verify(configObj, env).config; | ||
expect(result.nested.present).toEqual(env.PRESENT); | ||
}); | ||
it('runs the transform function and inserts the transformed value', () => { | ||
const transformed = ['hi', { there: ['this'] }, 'is', 'transformed']; | ||
const expected = expect.objectContaining(transformed); | ||
const configObj = { | ||
present: ['PRESENT', (_envVal) => transformed] | ||
}; | ||
const { present: result } = index_1.verify(configObj, env).config; | ||
expect(result).toEqual(expected); | ||
}); | ||
it('still returns an error if the env value is missing', () => { | ||
const configObj = { | ||
missing: ['MISSING', (envValue) => envValue] | ||
}; | ||
const { errors } = index_1.verify(configObj, env); | ||
expect(errors.length).toEqual(1); | ||
}); | ||
it('does not call the transform function if the env value is missing', () => { | ||
const transformFn = jest.fn(); | ||
const configObj = { | ||
missing: ['MISSING', transformFn] | ||
}; | ||
index_1.verify(configObj, env); | ||
expect(transformFn).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('strictVerfiy', () => { | ||
@@ -104,3 +149,58 @@ it('should throw an error on missing .env value', () => { | ||
}); | ||
describe('with insert()', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
}; | ||
it('does not error out when called with insert()', () => { | ||
const configObj = { | ||
nonEnvValue: index_1.insert('nonEnvValue') | ||
}; | ||
expect(() => index_1.verify(configObj, env)).not.toThrow(); | ||
}); | ||
it('inserts the given value into config object', () => { | ||
const configObj = { | ||
nonEnvValue: index_1.insert('nonEnvValue') | ||
}; | ||
const { nonEnvValue } = index_1.verify(configObj, env).config; | ||
expect(nonEnvValue).toEqual('nonEnvValue'); | ||
}); | ||
it('inserts given value in nested config object', () => { | ||
const configObj = { | ||
a: { | ||
nonEnvValue: index_1.insert('nonEnvValue') | ||
} | ||
}; | ||
const { config } = index_1.verify(configObj, env); | ||
expect(config.a.nonEnvValue).toEqual('nonEnvValue'); | ||
}); | ||
}); | ||
describe('integration of all features', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
}; | ||
it('mixes and matches features across nested config object', () => { | ||
const mixed = { | ||
present: 'PRESENT', | ||
transformed: ['PRESENT', (_value) => 'transformed'], | ||
inserted: index_1.insert('inserted') | ||
}; | ||
const configObj = { | ||
mixed, | ||
...mixed | ||
}; | ||
const expected = expect.objectContaining({ | ||
present: 'present', | ||
transformed: 'transformed', | ||
inserted: 'inserted', | ||
mixed: { | ||
present: 'present', | ||
transformed: 'transformed', | ||
inserted: 'inserted' | ||
} | ||
}); | ||
const { config } = index_1.verify(configObj, env); | ||
expect(config).toEqual(expected); | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=index.spec.js.map |
{ | ||
"name": "env-verifier", | ||
"version": "1.0.2", | ||
"version": "1.1.2", | ||
"description": "\"Make sure you have all your env variables!\"", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
111
README.md
# env-verifier | ||
Quickly verify that incoming variables from process.env aren't `undefined` | ||
Quickly verify that incoming variables from process.env aren't missing. | ||
@@ -24,34 +24,51 @@ [GitHub](https://github.com/pluralsight/env-verifier) | ||
There are two functions exposed - `verify` or `strictVerify`. Use `verify` when you whant to handle your own missing values, and `strictVerify` when you want us to throw a descriptive error. Simply change your code to something like this: | ||
to get up and running quickly with a verified config file, you can replace the above with something like this: | ||
```javascript | ||
const { config, errors } = verify({ | ||
const { verify } = require('env-verifier') | ||
const config = { | ||
database: { | ||
name: 'DB_NAME' | ||
host: 'DB_HOST' | ||
password: 'DB_PASSWORD' | ||
name: DB_NAME | ||
host: DB_HOST | ||
password: DB_PASSWORD | ||
}, | ||
baseUrl: 'BASE_URL' | ||
}, env) | ||
if (errors.length) { | ||
logger.error(errors) | ||
baseUrl: BASE_URL | ||
} | ||
module.exports = config | ||
const { config: builtConfig, errors } = verify(config) | ||
errors.foreach(error => console.error(error)) | ||
module.exports = builtConfig | ||
``` | ||
You can pass in an `env` parameter as long as its an object that is non-nested and has key value pairs with `undefined` or `string` as their value type | ||
This package exposes two verification functions - `verify` and `strictVerify`. Use `verify` (as seen above) when you want to handle reporting missing values, and `strictVerify` when you want us to throw a descriptive error. | ||
Function signatures (using typescript): | ||
You can pass in your own `env` object as a parameter as long as its an object that is non-nested and has key value pairs with `undefined` or `string` as their value type. | ||
Function signatures: | ||
```typescript | ||
interface Config { | ||
[key: string]: string | Config | ||
export interface TransformFn { | ||
(envValue: string): any | ||
} | ||
//see below | ||
// [envKeyName, TransformFn] | ||
export type TransformTuple = [string, TransformFn] | ||
interface ConfigWithEnvKeys { | ||
[key: string]: string | InsertValue | TransformTuple | ConfigWithEnvKeys | ||
} | ||
interface MappedConfig { | ||
[key: string]: string | undefined | Config | ||
[key: string]: any | string | undefined | Config | ||
} | ||
export interface VerifiedConfig { | ||
[key: string]: any | string | VerifiedConfig | ||
} | ||
interface Env { | ||
@@ -61,5 +78,7 @@ [key: string]: string | undefined | ||
function insert(value: any): InsertValue //see inserting arbitrary values below | ||
function verify(config: Config, env: Env = process.env): { config: MappedConfig, errors: string[] } | ||
function strictVerify(config: Config, env: Env = process.env): Config //Throws on .env miss | ||
function strictVerify(config: Config, env: Env = process.env): VerifiedConfig //See Errors section | ||
``` | ||
@@ -70,2 +89,3 @@ | ||
```javascript | ||
//will throw on undefined or empty string env variables | ||
module.exports = strictVerify({ | ||
@@ -81,2 +101,47 @@ database: { | ||
#### Arbitrary value insertion | ||
You may have values that aren't present on your `env` object, but that you would like to live in your config object, this can be achieved by using the `insert()` function. | ||
```javascript | ||
const { verify, insert } = require('env-verifier') | ||
module.exports = verify({ | ||
appName: insert('my_app') | ||
... // other env key names | ||
}) | ||
//exports: | ||
{ | ||
appName: 'my_app' | ||
... // other env values | ||
} | ||
``` | ||
#### Error generation and reporting | ||
Error reports are generated when an `env` variable is missing. An `env` variable is considered missing under the following circumstances: | ||
- `undefined` is returned from the `env` object. | ||
- an empty string, `''`, is returned from the `env` object. | ||
`strictVerify` will not throw an error on the first encountered missing `env` value. Instead it will continue in order to report all missing `env` variables. | ||
#### Variable Transformation (TransformTuple) | ||
Since process.env only returns strings, sometimes its necessary to transform those strings into something else (IE: transform the string `"true"` to a boolean `true`) | ||
This can be done by passing in an array (TransformTuple) containing the `env` variable name, and the function that you would like to use to transform the `env` variable value like so: | ||
```javascript | ||
const config = { | ||
useNewFeature: ['USE_NEW_FEATURE', trueOrFalse => trueOrFalse === 'true'], | ||
... //other env variables | ||
} | ||
verify(config) | ||
``` | ||
Transformation functions will not be run if its corresponding env value is missing. | ||
### Prerequisites | ||
@@ -88,3 +153,3 @@ | ||
One of these | ||
One of these: | ||
@@ -95,3 +160,3 @@ ```bash | ||
And one of these | ||
And one of these: | ||
@@ -106,5 +171,5 @@ ```javascript | ||
After you've ran `npm install`, just run `npm test` | ||
After you've ran `npm install`, just run `npm test`. | ||
We use jest as our testing framework | ||
We use jest as our testing framework. | ||
@@ -127,2 +192,2 @@ ## Contributing | ||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details | ||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. |
@@ -1,2 +0,2 @@ | ||
import { verify, strictVerify } from './index' | ||
import { verify, strictVerify, ConfigWithEnvKeys, insert } from './index' | ||
@@ -54,19 +54,81 @@ describe('env-verify', () => { | ||
const env = { | ||
a: 'A' | ||
PRESENT: 'present' | ||
} | ||
const config = { | ||
1: 'a', | ||
2: 'zz-top', | ||
1: 'PRESENT', | ||
2: 'MISSING', | ||
3: { | ||
6: { | ||
7: 'IM_ALSO_MISSING' | ||
7: 'MISSING' | ||
}, | ||
4: 'IM_MISSING', | ||
5: 'ME_TOO' | ||
4: 'MISSING', | ||
5: 'MISSING' | ||
} | ||
} | ||
expect(verify(config, env).errors.length).toEqual(4) | ||
const result = verify(config, env).errors | ||
expect(result.length).toEqual(4) | ||
}) | ||
}) | ||
}) | ||
describe('with transform functions', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
} | ||
it('allows a tuple with a string and transform function', () => { | ||
const configObj: ConfigWithEnvKeys = { | ||
present: ['PRESENT', (envVal: string): any => envVal] | ||
} | ||
expect(() => verify(configObj, env)).not.toThrow() | ||
}) | ||
it('allows the same tuple in a nested object', () => { | ||
const configObj: ConfigWithEnvKeys = { | ||
nested: { | ||
present: ['PRESENT', (envVal: string) => envVal] | ||
} | ||
} | ||
const result = verify(configObj, env).config | ||
expect(result.nested.present).toEqual(env.PRESENT) | ||
}) | ||
it('runs the transform function and inserts the transformed value', () => { | ||
const transformed = ['hi', { there: ['this'] }, 'is', 'transformed'] | ||
const expected = expect.objectContaining(transformed) | ||
const configObj: ConfigWithEnvKeys = { | ||
present: ['PRESENT', (_envVal: string) => transformed] | ||
} | ||
const { present: result } = verify(configObj, env).config | ||
expect(result).toEqual(expected) | ||
}) | ||
it('still returns an error if the env value is missing', () => { | ||
const configObj: ConfigWithEnvKeys = { | ||
missing: ['MISSING', (envValue: string) => envValue] | ||
} | ||
const { errors } = verify(configObj, env) | ||
expect(errors.length).toEqual(1) | ||
}) | ||
it('does not call the transform function if the env value is missing', () => { | ||
const transformFn = jest.fn() | ||
const configObj: any = { | ||
missing: ['MISSING', transformFn] | ||
} | ||
verify(configObj, env) | ||
expect(transformFn).not.toHaveBeenCalled() | ||
}) | ||
}) | ||
describe('strictVerfiy', () => { | ||
@@ -108,2 +170,70 @@ it('should throw an error on missing .env value', () => { | ||
}) | ||
describe('with insert()', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
} | ||
it('does not error out when called with insert()', () => { | ||
const configObj = { | ||
nonEnvValue: insert('nonEnvValue') | ||
} | ||
expect(() => verify(configObj, env)).not.toThrow() | ||
}) | ||
it('inserts the given value into config object', () => { | ||
const configObj = { | ||
nonEnvValue: insert('nonEnvValue') | ||
} | ||
const { nonEnvValue } = verify(configObj, env).config | ||
expect(nonEnvValue).toEqual('nonEnvValue') | ||
}) | ||
it('inserts given value in nested config object', () => { | ||
const configObj = { | ||
a: { | ||
nonEnvValue: insert('nonEnvValue') | ||
} | ||
} | ||
const { config } = verify(configObj, env) | ||
expect(config.a.nonEnvValue).toEqual('nonEnvValue') | ||
}) | ||
}) | ||
describe('integration of all features', () => { | ||
const env = { | ||
PRESENT: 'present' | ||
} | ||
it('mixes and matches features across nested config object', () => { | ||
const mixed: ConfigWithEnvKeys = { | ||
present: 'PRESENT', | ||
transformed: ['PRESENT', (_value: string) => 'transformed'], | ||
inserted: insert('inserted') | ||
} | ||
const configObj = { | ||
mixed, | ||
...mixed | ||
} | ||
const expected = expect.objectContaining({ | ||
present: 'present', | ||
transformed: 'transformed', | ||
inserted: 'inserted', | ||
mixed: { | ||
present: 'present', | ||
transformed: 'transformed', | ||
inserted: 'inserted' | ||
} | ||
}) | ||
const { config } = verify(configObj, env) | ||
expect(config).toEqual(expected) | ||
}) | ||
}) | ||
}) |
export interface MappedConfig { | ||
[key: string]: string | undefined | MappedConfig | ||
[key: string]: any | string | undefined | MappedConfig | ||
} | ||
interface ConfigWithEnvKeys { | ||
[key: string]: string | ConfigWithEnvKeys | ||
export interface TransformFn { | ||
(envValue: string): any | ||
} | ||
export type TransformTuple = [string, TransformFn] | ||
export interface ConfigWithEnvKeys { | ||
[key: string]: string | InsertValue | TransformTuple | ConfigWithEnvKeys | ||
} | ||
interface VerifyParamCollection { | ||
@@ -16,5 +23,12 @@ config: ConfigWithEnvKeys | ||
export interface VerifiedConfig { | ||
[key: string]: string | VerifiedConfig | ||
[key: string]: any | string | VerifiedConfig | ||
} | ||
class InsertValue { | ||
value: any | ||
constructor(value: any) { | ||
this.value = value | ||
} | ||
} | ||
const recursiveVerify = ({ | ||
@@ -30,12 +44,29 @@ config, | ||
if (typeof value === 'string') { | ||
const envValue = env[value] | ||
const getValueFromEnv = (key: string): string => { | ||
const envValue = env[key] | ||
if (envValue === undefined || envValue.length === 0) { | ||
errors.push( | ||
new Error( | ||
`environment value ${value} is missing from config object at ${subPath}` | ||
`environment value ${key} is missing from config object at ${subPath}` | ||
) | ||
) | ||
return undefined | ||
} | ||
return { [key]: envValue } as ConfigWithEnvKeys | ||
return envValue | ||
} | ||
if (value instanceof InsertValue) { | ||
return { [key]: value.value } | ||
} else if (Array.isArray(value)) { | ||
const [envKey, transformFn] = value as TransformTuple | ||
const envValue = getValueFromEnv(envKey) | ||
const transforedValue = envValue && transformFn(envValue) | ||
return { [key]: transforedValue } | ||
} else if (typeof value === 'string') { | ||
const envValue = getValueFromEnv(value as string) | ||
return { [key]: envValue } | ||
} else { | ||
@@ -86,1 +117,5 @@ const { errors: subErrors, config: subConfig } = recursiveVerify({ | ||
} | ||
export function insert(value: any): InsertValue { | ||
return new InsertValue(value) | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
48559
16
627
186