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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
48559
16
627
186