Skip to content

Commit

Permalink
Merge bc7a78e into a95ac7b
Browse files Browse the repository at this point in the history
  • Loading branch information
evanshortiss committed Sep 9, 2019
2 parents a95ac7b + bc7a78e commit 107711d
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 101 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 5.1.0 (09/09/19)
* Ability to add custom accessors in PR #72 (thanks @todofixthis)
* Improved TypeScript tests
* Fixed warning generated by husky

## 5.0.0 (14/06/19)
* Return values from `asArray()` are now more intuitive & consitent
* `asUrlString()` and `asUrlObject`now use the built-in `URL` class in Node.js
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,34 @@ let validEmail = env.get('ADMIN').checkEmail()
let invalidEmail = env.get('ADMIN').checkEmail('github.com')
```

This feature is also available for TypeScript users. The `ExtensionFn` type is
expoed to help in the creation of these new accessors.

```ts
import { from, ExtensionFn, EnvVarError } from 'env-var'

// Environment variable that we will use for this example:
process.env.ADMIN = 'admin@example.com'

const checkEmail: ExtensionFn<string> = (value) => {
const split = String(value).split('@')

// Validating email addresses is hard.
if (split.length !== 2) {
throw new Error('must contain exactly one "@"')
}

return value
}

const env = from(process.env, {
checkEmail
})

// Returns the email string if it's valid, otherwise it will throw
env.get('ADMIN').checkEmail()
```

### get([varname, [default]])
You can call this function 3 different ways:

Expand Down
28 changes: 21 additions & 7 deletions env-var.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,9 @@ interface IOptionalVariable {
asEnum: (validValues: string[]) => string|undefined;
}

declare class EnvVarError extends Error {}
export class EnvVarError extends Error {}

interface IEnv {
interface IEnv<PresentVariable, OptionalVariable> {
/**
* Returns an object containing all current environment variables
*/
Expand All @@ -218,18 +218,21 @@ interface IEnv {
/**
* Gets an environment variable that is possibly not set to a value
*/
get (varName: string): IOptionalVariable;
get (varName: string): OptionalVariable;

/**
* Gets an environment variable, using the default value if it is not already set
*/
get (varName: string, defaultValue: string): IPresentVariable;
get (varName: string, defaultValue: string): PresentVariable;

/**
* Returns a new env-var instance, where the given object is used for the environment variable mapping.
* Use this when writing unit tests or in environments outside node.js.
*/
from(values: NodeJS.ProcessEnv): IEnv;
from<T extends Extensions, K extends keyof T>(values: NodeJS.ProcessEnv, extensions?: T): IEnv<
IPresentVariable & Record<K, (...args: any[]) => ReturnType<T[K]>>,
IOptionalVariable & Record<K, (...args: any[]) => ReturnType<T[K]>|undefined>
>;

/**
* This is the error type used to represent error returned by this module.
Expand All @@ -238,5 +241,16 @@ interface IEnv {
EnvVarError: EnvVarError
}

declare const env: IEnv;
export = env;
export type Extensions = {
[key: string]: ExtensionFn<any>
}
export type RaiseErrorFn = (error: string) => void
export type ExtensionFn<T> = (value: string, ...args: any[]) => T

export function get(): {[varName: string]: string}
export function get(varName: string): IOptionalVariable;
export function get(varName: string, defaultValue: string): IPresentVariable;
export function from<T extends Extensions, K extends keyof T>(values: NodeJS.ProcessEnv, extensions?: T): IEnv<
IPresentVariable & Record<K, (...args: any[]) => ReturnType<T[K]>>,
IOptionalVariable & Record<K, (...args: any[]) => ReturnType<T[K]>|undefined>
>;
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "env-var",
"version": "5.0.0",
"version": "5.1.0",
"description": "Verification, sanatization, and type coercion for environment variables in Node.js",
"main": "env-var.js",
"typings": "env-var.d.ts",
Expand All @@ -9,10 +9,14 @@
"coverage": "nyc mocha test/ && nyc report --reporter=lcov",
"check-coverage": "nyc check-coverage --statements 100 --branches 100 --functions 100 --lines 100",
"unit": "mocha test/",
"precommit": "npm run lint && npm run unit",
"lint": "standard example/*.js *.js \"lib/**/*.js\" test/*.js --fix",
"test": "npm run unit && npm run coverage && npm run check-coverage && npm run lint && npm run ts-verify",
"ts-verify": "tsc test/index.ts --noEmit --target es5"
"ts-verify": "tsc && node test/types/index.js"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint && npm run unit"
}
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -59,6 +63,6 @@
"typescript": "~3.1.3"
},
"engines": {
"node": ">=6.0"
"node": ">=8"
}
}
88 changes: 0 additions & 88 deletions test/index.ts

This file was deleted.

2 changes: 2 additions & 0 deletions test/types/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.js
*.js.map
125 changes: 125 additions & 0 deletions test/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@

import * as env from '../../';
import * as assert from 'assert';
import * as url from 'url';

function test () {
// BASE 64
process.env.BASE64 = 'aGVsbG8='
assert.equal(
env.get('BASE64').required().convertFromBase64().asString(),
'hello'
)

// STRINGS
process.env.STRING = 'string'
assert.equal(env.get('STRING').required().asString(), process.env.STRING)
assert.equal(env.get('SOME_STRING').asString(), undefined)


// INTEGERS
process.env.INT = '0'
process.env.INT_POSITIVE = '2'
process.env.INT_NEGATIVE = '-2'

const intVal = env.get('INT').required().asInt();
const intValPositive = env.get('INT_POSITIVE').asIntPositive();
const intValNegative = env.get('INT_NEGATIVE').asIntNegative();

assert.equal(intVal, 0)
assert.equal(intValPositive, 2)
assert.equal(intValNegative, -2)


// FLOATs
process.env.FLOAT = '13.234234'
process.env.FLOAT_POSITIVE = '13.234234'
process.env.FLOAT_NEGATIVE = '-13.234234'

const floatVal = env.get('FLOAT').required().asFloat();
const floatValPositive = env.get('FLOAT_POSITIVE').asFloatPositive();
const floatValNegative = env.get('FLOAT_NEGATIVE').asFloatNegative();

assert.equal(floatVal, 13.234234)
assert.equal(floatValPositive, 13.234234)
assert.equal(floatValNegative, -13.234234)


// BOOLEAN
process.env.BOOL = '0'
process.env.BOOL_STRICT = 'true'

const asBool = env.get('BOOL').required().asBool();
const asBoolStrict = env.get('BOOL_STRICT').asBoolStrict();

assert.equal(asBool, false)
assert.equal(asBoolStrict, true)


// ARRAY
process.env.ARRAY_COMMA = '1,2,3'
process.env.ARRAY_HYPHEN = '1-2-3'

assert.deepEqual(env.get('ARRAY_COMMA').asArray(), ['1', '2', '3'])
assert.deepEqual(env.get('ARRAY_HYPHEN').asArray('-'), ['1', '2', '3'])


// JSON
process.env.JSON_OBJECT = JSON.stringify({ test: 'testing' })
process.env.JSON_ARRAY = JSON.stringify([1,2,3])

assert.deepEqual(env.get('JSON_OBJECT').asJson(), { test: 'testing' })
assert.deepEqual(env.get('JSON_OBJECT').asJsonObject(), { test: 'testing' })
assert.deepEqual(env.get('JSON_ARRAY').asJsonArray(), [1,2,3])


// URLS
process.env.URL_STRING = 'http://google.com/'

assert.deepEqual(env.get('URL_STRING').asUrlObject(), new url.URL(process.env.URL_STRING))
assert.equal(env.get('URL_STRING').asUrlString(), 'http://google.com/')

// FROM
const newEnv = env.from(process.env)
assert.equal(newEnv.get('STRING').required().asString(), process.env.STRING)
assert.equal(newEnv.get('SOME_STRING').asString(), undefined)

// FROM WITH EXTENSIONS
interface EmailParts {
username: string
domain: string
}

const asEmailComponents: env.ExtensionFn<EmailParts> = (value) => {
const parts = value.split('@')

if (parts.length != 2) {
throw new env.EnvVarError('probably not an email')
} else {
return {
username: parts[0],
domain: parts[1]
}
}
}

const extrasEnv = env.from({
EMAIL: 'hello@example.com'
}, {
asEmailComponents
})

assert.equal(
extrasEnv.get('MISSING_EMAIL').asEmailComponents(),
undefined
)
assert.deepEqual(
extrasEnv.get('EMAIL').asEmailComponents(),
{
domain: 'example.com',
username: 'hello'
}
)
}

test()
6 changes: 4 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"noImplicitAny": true,
"target": "es5",
"moduleResolution": "node",
"skipLibCheck": true
}
"skipLibCheck": true,
"strict": true
},
"files": ["test/types/index.ts"]
}

0 comments on commit 107711d

Please sign in to comment.