├── .npmrc ├── .eslintignore ├── .gitignore ├── .npmignore ├── .huskyrc.json ├── .prettierignore ├── src ├── index.umd.ts ├── utils │ ├── index.ts │ ├── options.ts │ ├── format.ts │ └── sanitize.ts ├── typings.ts └── index.ts ├── .prettierrc ├── test ├── specs │ ├── default-options.spec.ts │ ├── __snapshots__ │ │ ├── default-options.spec.ts.snap │ │ └── basic.spec.ts.snap │ ├── basic.spec.ts │ ├── sanitize │ │ └── sanitize.spec.ts │ ├── format │ │ └── format.spec.ts │ └── separator │ │ └── separator.spec.ts ├── .eslintrc.json └── test-utils.ts ├── .lintstagedrc.json ├── .vscode └── settings.json ├── jest.config.js ├── tsconfig.json ├── .eslintrc.json ├── LICENSE ├── rollup.config.ts ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | dist/ 4 | 5 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .rpt2_cache/ 2 | node_modules/ 3 | src/ 4 | test/ 5 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | dist/ 4 | 5 | coverage/ 6 | 7 | *.min.js* 8 | 9 | -------------------------------------------------------------------------------- /src/index.umd.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore file */ 2 | import { readEnv } from '.'; 3 | 4 | export default readEnv; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "useTabs": false 6 | } 7 | -------------------------------------------------------------------------------- /test/specs/default-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { defaultOptions } from '../../src/utils'; 2 | 3 | it('should match the snapshot', () => { 4 | expect(defaultOptions).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,ts}": [ 3 | "prettier --write", 4 | "eslint", 5 | "jest --findRelatedTests --no-coverage", 6 | "git add" 7 | ], 8 | "*.{json,md}": ["prettier --write", "git add"] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.background": "#1C3404", 4 | "titleBar.activeBackground": "#284805", 5 | "titleBar.activeForeground": "#F3FDE8" 6 | }, 7 | "typescript.validate.enable": true 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './options'; 2 | export * from './format'; 3 | export * from './sanitize'; 4 | 5 | const trimLeft = (str: string, charList: string): string => 6 | str.replace(new RegExp(`^[${charList}]+`), ''); 7 | 8 | export { trimLeft }; 9 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../.eslintrc.json", "plugin:jest/recommended"], 3 | "plugins": ["jest"], 4 | "env": { 5 | "jest/globals": true 6 | }, 7 | "rules": { 8 | "@typescript-eslint/explicit-function-return-type": "off", 9 | "@typescript-eslint/camelcase": "off" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/specs/__snapshots__/default-options.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should match the snapshot 1`] = ` 4 | Object { 5 | "format": "camelcase", 6 | "includePrefix": false, 7 | "sanitize": Object { 8 | "array": true, 9 | "bool": true, 10 | "float": true, 11 | "int": true, 12 | "object": true, 13 | }, 14 | "separator": "__", 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // Automatically clear mock calls and instances between every test 6 | clearMocks: true, 7 | 8 | collectCoverage: true, 9 | 10 | coveragePathIgnorePatterns: ['/node_modules|dist/', 'rollup.config.ts'], 11 | 12 | collectCoverageFrom: ['src/**/*.ts'], 13 | 14 | transform: { 15 | '^.+\\.tsx?$': 'ts-jest', 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "esModuleInterop": true, 5 | "target": "es5", 6 | "module": "es2015", 7 | "lib": ["es2015", "es2016", "es2017", "es2018", "dom"], 8 | "strict": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "composite": true, 12 | "stripInternal": true, 13 | "rootDir": "./src/", 14 | "typeRoots": ["node_modules/@types"], 15 | "tsBuildInfoFile": ".tsc_cache/.tsbuildinfo" 16 | }, 17 | "include": ["./src/"], 18 | "exclude": ["./node_modules/", "./test/"] 19 | } 20 | -------------------------------------------------------------------------------- /src/typings.ts: -------------------------------------------------------------------------------- 1 | type FormatFunction = (str: string) => string; 2 | 3 | interface Source { 4 | [key: string]: string | undefined; 5 | } 6 | 7 | interface SanitizeOptions { 8 | object?: boolean; 9 | array?: boolean; 10 | bool?: boolean; 11 | int?: boolean; 12 | float?: boolean; 13 | } 14 | 15 | type Format = 'camelcase' | 'pascalcase' | 'lowercase' | 'uppercase'; 16 | 17 | interface ReadEnvOptions { 18 | source?: Source; 19 | separator: boolean | string; 20 | includePrefix: boolean; 21 | format: boolean | Format | FormatFunction; 22 | sanitize: boolean | SanitizeOptions; 23 | } 24 | 25 | type ReadEnvResult = Record; 26 | 27 | export { 28 | ReadEnvOptions, 29 | Format, 30 | SanitizeOptions, 31 | FormatFunction, 32 | ReadEnvResult, 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/options.ts: -------------------------------------------------------------------------------- 1 | import { ReadEnvOptions, SanitizeOptions } from '../typings'; 2 | 3 | const defaultOptions: ReadEnvOptions = { 4 | format: 'camelcase', 5 | separator: '__', 6 | sanitize: { 7 | object: true, 8 | array: true, 9 | bool: true, 10 | int: true, 11 | float: true, 12 | }, 13 | includePrefix: false, 14 | }; 15 | 16 | const getOptions = (userOptions: Partial): ReadEnvOptions => { 17 | if (typeof userOptions.sanitize === 'object') { 18 | // eslint-disable-next-line no-param-reassign 19 | userOptions.sanitize = { 20 | ...(defaultOptions.sanitize as SanitizeOptions), 21 | ...userOptions.sanitize, 22 | }; 23 | } 24 | return { ...defaultOptions, ...userOptions }; 25 | }; 26 | 27 | export { defaultOptions, getOptions }; 28 | -------------------------------------------------------------------------------- /src/utils/format.ts: -------------------------------------------------------------------------------- 1 | import camelcase from 'camelcase'; 2 | import { FormatFunction } from '../typings'; 3 | 4 | const formatters: Record = { 5 | camelcase: (str: string): string => camelcase(str.toLowerCase()), 6 | pascalcase: (str: string): string => 7 | camelcase(str.toLowerCase(), { pascalCase: true }), 8 | lowercase: (str: string): string => str.toLowerCase(), 9 | uppercase: (str: string): string => str.toUpperCase(), 10 | }; 11 | 12 | const formatKey = ( 13 | key: string, 14 | format: boolean | string | FormatFunction, 15 | ): string => { 16 | if (typeof format === 'string') { 17 | return formatters[format](key); 18 | } 19 | 20 | if (typeof format === 'function') { 21 | return format(key); 22 | } 23 | 24 | return key; 25 | }; 26 | 27 | export { formatKey }; 28 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "import"], 4 | "extends": [ 5 | "airbnb-base", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:import/errors", 8 | "plugin:import/warnings", 9 | "plugin:import/typescript" 10 | ], 11 | "rules": { 12 | "no-undef": "off", 13 | "implicit-arrow-linebreak": "off", 14 | "operator-linebreak": "off", 15 | "function-paren-newline": "off", 16 | "arrow-parens": "off", 17 | "no-underscore-dangle": "off", 18 | "import/group-exports": 2, 19 | "import/prefer-default-export": "off", 20 | "import/extensions": "off", 21 | "import/namespace": [2, { "allowComputed": true }], 22 | "import/no-extraneous-dependencies": [ 23 | "error", 24 | { "devDependencies": ["**/test/**/*.ts"] } 25 | ], 26 | "@typescript-eslint/indent": ["error", 2], 27 | "object-curly-newline": [ 28 | "error", 29 | { 30 | "ExportDeclaration": { "multiline": true, "minProperties": 5 } 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/specs/__snapshots__/basic.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Given options argument was not provided should sanitize all the values 1`] = ` 4 | Object { 5 | "array": Array [ 6 | 1, 7 | 2, 8 | 3, 9 | "string", 10 | Object { 11 | "prop": "value", 12 | }, 13 | 5.2, 14 | ], 15 | "convertsCamelcase": "camelCase", 16 | "convertsLowercase": "lowercase", 17 | "convertsUppercase": "uppercase", 18 | "dontTransformMe": "dontTransform", 19 | "exampleKey": "exampleExample", 20 | "false": false, 21 | "float": 5.2456, 22 | "floatZero": 0, 23 | "int": 5, 24 | "intZero": 0, 25 | "invalidArray": "[1,2,3, \\"string\\", ]{\\"prop\\": \\"value\\"}, 5.2]", 26 | "invalidObject": "{\\"prop\\": }\\"value\\"}", 27 | "negativeFloat": -2.4567, 28 | "negativeFloatZero": -0, 29 | "negativeInt": -11, 30 | "negativeIntZero": -0, 31 | "object": Object { 32 | "prop": "value", 33 | }, 34 | "string": "example", 35 | "subInt": 7, 36 | "subString": "subExample", 37 | "true": true, 38 | "undefined": undefined, 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mehmet Yatkı 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import typescript from 'rollup-plugin-typescript2'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-var-requires 8 | const pkg = require('./package.json'); 9 | 10 | const config = [ 11 | { 12 | input: 'src/index.ts', 13 | output: [ 14 | { 15 | file: pkg.main, 16 | format: 'cjs', 17 | sourcemap: true, 18 | }, 19 | { 20 | file: pkg.module, 21 | format: 'esm', 22 | sourcemap: true, 23 | }, 24 | ], 25 | external: Object.keys(pkg.dependencies), 26 | plugins: [typescript(), resolve(), terser({ output: { comments: false } })], 27 | }, 28 | { 29 | input: 'src/index.umd.ts', 30 | output: { 31 | file: pkg.browser, 32 | format: 'umd', 33 | sourcemap: true, 34 | name: 'readEnv', 35 | }, 36 | plugins: [ 37 | typescript(), 38 | commonjs(), 39 | resolve(), 40 | terser({ output: { comments: false } }), 41 | ], 42 | }, 43 | ]; 44 | 45 | export default config; 46 | -------------------------------------------------------------------------------- /src/utils/sanitize.ts: -------------------------------------------------------------------------------- 1 | import { SanitizeOptions } from '../typings'; 2 | 3 | const isObject = (value: string): boolean => 4 | value[0] === '{' && value[value.length - 1] === '}'; 5 | 6 | const isArray = (value: string): boolean => 7 | value[0] === '[' && value[value.length - 1] === ']'; 8 | 9 | const isInt = (value: string): boolean => /^-?\d+$/.test(value); 10 | 11 | const isFloat = (value: string): boolean => /^-?\d*(\.\d+)$/.test(value); 12 | const isBool = (value: string): boolean => { 13 | const boolValue = value.toLowerCase(); 14 | return boolValue === 'true' || boolValue === 'false'; 15 | }; 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 18 | const sanitize = (value: string, options: SanitizeOptions): any => { 19 | // Sanitize Object 20 | if (options.object && isObject(value)) { 21 | try { 22 | return JSON.parse(value); 23 | } catch (err) { 24 | return value; 25 | } 26 | } 27 | 28 | // Sanitize Array 29 | if (options.array && isArray(value)) { 30 | try { 31 | return JSON.parse(value); 32 | } catch (err) { 33 | return value; 34 | } 35 | } 36 | 37 | // Sanitize Integer 38 | if (options.int && isInt(value)) { 39 | return parseInt(value, 10); 40 | } 41 | 42 | // Sanitize Float 43 | if (options.float && isFloat(value)) { 44 | return parseFloat(value); 45 | } 46 | 47 | // Sanitize Boolean 48 | if (options.bool && isBool(value)) { 49 | return value.toLowerCase() === 'true'; 50 | } 51 | 52 | // Return raw value 53 | return value; 54 | }; 55 | 56 | export { sanitize }; 57 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { ReadEnvOptions, ReadEnvResult } from './typings'; 2 | import { getOptions, sanitize, formatKey, trimLeft } from './utils'; 3 | 4 | const readEnv = ( 5 | prefix?: string, 6 | options: Partial = {}, 7 | ): ReadEnvResult => { 8 | // Options 9 | const { 10 | format, 11 | source = process.env, 12 | includePrefix, 13 | sanitize: sanitizeOptions, 14 | separator, 15 | } = getOptions(options); 16 | 17 | let keys = Object.keys(source); 18 | 19 | // Filter keys by prefix 20 | if (typeof prefix === 'string' && prefix) { 21 | keys = keys.filter((value) => value.indexOf(prefix) === 0); 22 | } 23 | 24 | // Process keys 25 | return keys.reduce((result, key) => { 26 | let envValue = source[key]?.trim(); 27 | let envKey = key; 28 | 29 | // Sanitize 30 | if (typeof sanitizeOptions === 'object' && envValue) { 31 | envValue = sanitize(envValue, sanitizeOptions); 32 | } 33 | 34 | // Remove Prefix 35 | if (!includePrefix && typeof prefix === 'string' && prefix) { 36 | envKey = envKey.replace(prefix, ''); 37 | } 38 | 39 | // Trim left underscore 40 | envKey = trimLeft(envKey, '_'); 41 | 42 | let deepKeys = [envKey]; 43 | // Process deep object 44 | if (typeof separator === 'string' && separator) { 45 | deepKeys = envKey.split(separator); 46 | } 47 | 48 | deepKeys.reduce((acc, item, index) => { 49 | const deepKey = formatKey(item, format); 50 | if (index === deepKeys.length - 1) { 51 | acc[deepKey] = envValue; 52 | } else if ( 53 | typeof acc[deepKey] !== 'object' || 54 | Array.isArray(acc[deepKey]) 55 | ) { 56 | acc[deepKey] = {}; 57 | } 58 | return acc[deepKey]; 59 | }, result); 60 | 61 | // and DONE! 62 | return result; 63 | }, {} as ReadEnvResult); 64 | }; 65 | 66 | export { readEnv }; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "read-env", 3 | "version": "2.0.0", 4 | "description": "Transform environment variables into JSON object with sanitized values.", 5 | "main": "dist/index.js", 6 | "module": "dist/index.esm.js", 7 | "browser": "dist/index.umd.js", 8 | "types": "dist/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com:yatki/read-env.git" 12 | }, 13 | "files": [ 14 | "dist/", 15 | "package.json", 16 | "README.md", 17 | "LICENCE" 18 | ], 19 | "keywords": [ 20 | "env", 21 | "environment variables", 22 | "parse", 23 | "JSON.parse", 24 | "sanitize", 25 | "process.env", 26 | "config", 27 | "conf", 28 | "read" 29 | ], 30 | "author": "Mehmet Yatkı", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/yatki/read-env/issues" 34 | }, 35 | "homepage": "https://github.com/yatki/read-env", 36 | "scripts": { 37 | "preversion": "npm run lint && npm test", 38 | "prepublishOnly": "npm run lint && npm test && npm run build", 39 | "prebuild": "rimraf dist", 40 | "build": "rollup -c rollup.config.ts", 41 | "lint": "eslint .", 42 | "test": "jest", 43 | "coveralls": "jest --coverage --coverageReporters=text-lcov | coveralls" 44 | }, 45 | "devDependencies": { 46 | "@rollup/plugin-commonjs": "15.0.0", 47 | "@rollup/plugin-node-resolve": "9.0.0", 48 | "@types/jest": "26.0.10", 49 | "@types/node": "14.6.0", 50 | "@typescript-eslint/eslint-plugin": "3.9.1", 51 | "@typescript-eslint/parser": "3.9.1", 52 | "coveralls": "3.1.0", 53 | "eslint": "7.7.0", 54 | "eslint-config-airbnb-base": "14.2.0", 55 | "eslint-plugin-import": "2.22.0", 56 | "eslint-plugin-jest": "23.20.0", 57 | "husky": "4.2.5", 58 | "jest": "26.4.1", 59 | "lint-staged": "10.2.11", 60 | "prettier": "2.0.5", 61 | "rimraf": "3.0.2", 62 | "rollup": "2.26.4", 63 | "rollup-plugin-terser": "7.0.0", 64 | "rollup-plugin-typescript2": "0.27.2", 65 | "ts-jest": "26.2.0", 66 | "typescript": "4.0.2" 67 | }, 68 | "dependencies": { 69 | "@types/camelcase": "5.2.0", 70 | "camelcase": "6.0.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/specs/basic.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | initEnvVariables, 3 | ALL_INPUT, 4 | ALL_OUTPUT, 5 | ucFirst, 6 | } from '../test-utils'; 7 | import { readEnv } from '../../src'; 8 | import { defaultOptions } from '../../src/utils'; 9 | 10 | describe('Given prefix argument was not provided', () => { 11 | it('should format and sanitize all given source without filtering', () => { 12 | // Arrange 13 | const testInput = { 14 | ...ALL_INPUT, 15 | NOT_MATCHING_KEY1: 'value1', 16 | NOT_MATCHING_KEY2: 'value2', 17 | }; 18 | const testOutput = Object.keys(ALL_OUTPUT).reduce( 19 | (acc: Record, item) => { 20 | acc[`example${ucFirst(item, false)}`] = ALL_OUTPUT[item]; 21 | return acc; 22 | }, 23 | {}, 24 | ); 25 | 26 | // Act 27 | const result = readEnv('', { source: testInput }); 28 | 29 | // Assert 30 | expect(result).toEqual({ 31 | ...testOutput, 32 | notMatchingKey1: 'value1', 33 | notMatchingKey2: 'value2', 34 | }); 35 | }); 36 | }); 37 | 38 | describe('Given options argument was not provided', () => { 39 | it('should use default options and read "process.env" as source', () => { 40 | // Arrange 41 | initEnvVariables(); 42 | const resultWithDefaultOptions = readEnv('EXAMPLE', defaultOptions); 43 | 44 | // Act 45 | const resultWithoutOptions = readEnv('EXAMPLE'); 46 | 47 | // Assert 48 | expect(resultWithoutOptions).toEqual(resultWithDefaultOptions); 49 | }); 50 | 51 | it('should sanitize all the values', () => { 52 | // Act 53 | const result = readEnv('EXAMPLE', { 54 | source: ALL_INPUT, 55 | }); 56 | 57 | // Assert 58 | expect(result).toEqual(ALL_OUTPUT); 59 | expect(result).toMatchSnapshot(); 60 | }); 61 | 62 | it('should trim the underscores after the prefix', () => { 63 | // Arrange 64 | const testInput = { 65 | EXAMPLE__mY_key1: 'dummyValue', 66 | EXAMPLE_____MY_KEY2: 'dummyValue', 67 | }; 68 | const testOutput = { 69 | myKey1: 'dummyValue', 70 | myKey2: 'dummyValue', 71 | }; 72 | // Act 73 | const result = readEnv('EXAMPLE', { 74 | source: testInput, 75 | }); 76 | 77 | // Assert 78 | expect(result).toEqual(testOutput); 79 | }); 80 | 81 | it('should trim the env variable value', () => { 82 | // Arrange 83 | const testInput = { 84 | EXAMPLE__mY_key1: ' dummyValue1 ', 85 | EXAMPLE_____MY_KEY2: ' dummyValue 2 ', 86 | }; 87 | const testOutput = { 88 | myKey1: 'dummyValue1', 89 | myKey2: 'dummyValue 2', 90 | }; 91 | // Act 92 | const result = readEnv('EXAMPLE', { 93 | source: testInput, 94 | }); 95 | 96 | // Assert 97 | expect(result).toEqual(testOutput); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/test-utils.ts: -------------------------------------------------------------------------------- 1 | type Input = Record; 2 | type Output = Record; 3 | 4 | const SANITIZE_INPUT = { 5 | EXAMPLE_OBJECT: '{"prop": "value"}', 6 | EXAMPLE_ARRAY: '[1,2,3, "string", {"prop": "value"}, 5.2]', 7 | EXAMPLE_INVALID_OBJECT: '{"prop": }"value"}', 8 | EXAMPLE_INVALID_ARRAY: '[1,2,3, "string", ]{"prop": "value"}, 5.2]', 9 | EXAMPLE_TRUE: 'true', 10 | EXAMPLE_FALSE: 'false', 11 | EXAMPLE_INT: '5', 12 | EXAMPLE_NEGATIVE_INT: '-11', 13 | EXAMPLE_FLOAT: '5.2456', 14 | EXAMPLE_NEGATIVE_FLOAT: '-2.4567', 15 | EXAMPLE_INT_ZERO: '0', 16 | EXAMPLE_FLOAT_ZERO: '0.00', 17 | EXAMPLE_NEGATIVE_INT_ZERO: '-0', 18 | EXAMPLE_NEGATIVE_FLOAT_ZERO: '-0.00', 19 | EXAMPLE_STRING: 'example', 20 | }; 21 | 22 | const SANITIZE_OUTPUT: Output = { 23 | object: { prop: 'value' }, 24 | array: [1, 2, 3, 'string', { prop: 'value' }, 5.2], 25 | invalidObject: '{"prop": }"value"}', 26 | invalidArray: '[1,2,3, "string", ]{"prop": "value"}, 5.2]', 27 | true: true, 28 | false: false, 29 | int: 5, 30 | negativeInt: -11, 31 | float: 5.2456, 32 | negativeFloat: -2.4567, 33 | intZero: 0, 34 | floatZero: 0, 35 | negativeIntZero: -0, 36 | negativeFloatZero: -0, 37 | string: 'example', 38 | }; 39 | 40 | const FORMAT_INPUT = { 41 | EXAMPLE_CONVERTS_CAMELCASE: 'camelCase', 42 | EXAMPLE_CONVERTS_LOWERCASE: 'lowercase', 43 | EXAMPLE_converts_uppercase: 'uppercase', 44 | EXAMPLE_EXAMPLE_KEY: 'exampleExample', 45 | EXAMPLE_DoNT_TRanSFoRM_ME: 'dontTransform', 46 | EXAMPLE_SUB_INT: '7', 47 | EXAMPLE_SUB_STRING: 'subExample', 48 | EXAMPLE_UNDEFINED: undefined, 49 | }; 50 | 51 | const FORMAT_OUTPUT: Output = { 52 | convertsCamelcase: 'camelCase', 53 | convertsLowercase: 'lowercase', 54 | convertsUppercase: 'uppercase', 55 | exampleKey: 'exampleExample', 56 | dontTransformMe: 'dontTransform', 57 | subInt: 7, 58 | subString: 'subExample', 59 | undefined, 60 | }; 61 | 62 | const ALL_INPUT = { ...SANITIZE_INPUT, ...FORMAT_INPUT }; 63 | const ALL_OUTPUT: Output = { ...SANITIZE_OUTPUT, ...FORMAT_OUTPUT }; 64 | 65 | const cleanEnvVariables = (vars: Input = ALL_INPUT) => { 66 | const testVariableKeys = Object.keys(vars); 67 | testVariableKeys.forEach((key) => { 68 | delete process.env[key]; 69 | }); 70 | }; 71 | 72 | const initEnvVariables = (vars: Input = ALL_INPUT) => { 73 | cleanEnvVariables(vars); 74 | 75 | const testVariableKeys = Object.keys(vars); 76 | testVariableKeys.forEach((key) => { 77 | process.env[key] = vars[key]; 78 | }); 79 | }; 80 | 81 | const ucFirst = (string: string, lowerRest = true): string => 82 | string.charAt(0).toUpperCase() + 83 | (lowerRest ? string.slice(1).toLowerCase() : string.slice(1)); 84 | 85 | export { 86 | SANITIZE_INPUT, 87 | SANITIZE_OUTPUT, 88 | FORMAT_INPUT, 89 | FORMAT_OUTPUT, 90 | ALL_INPUT, 91 | ALL_OUTPUT, 92 | initEnvVariables, 93 | cleanEnvVariables, 94 | ucFirst, 95 | }; 96 | -------------------------------------------------------------------------------- /test/specs/sanitize/sanitize.spec.ts: -------------------------------------------------------------------------------- 1 | import { readEnv } from '../../../src'; 2 | import { SANITIZE_INPUT, SANITIZE_OUTPUT } from '../../test-utils'; 3 | 4 | describe('Given sanitize is set to false', () => { 5 | it('should return all the values as it is', () => { 6 | // Act 7 | const result = readEnv('EXAMPLE', { 8 | source: SANITIZE_INPUT, 9 | sanitize: false, 10 | format: false, 11 | includePrefix: true, 12 | }); 13 | 14 | // Assert 15 | expect(result).toEqual(SANITIZE_INPUT); 16 | }); 17 | }); 18 | 19 | describe('Given sanitize.object is set to false', () => { 20 | it('should return object json values as it is', () => { 21 | // Arrange 22 | const testOutput = { 23 | ...SANITIZE_OUTPUT, 24 | object: SANITIZE_INPUT.EXAMPLE_OBJECT, 25 | }; 26 | 27 | // Act 28 | const result = readEnv('EXAMPLE', { 29 | source: SANITIZE_INPUT, 30 | sanitize: { 31 | object: false, 32 | }, 33 | }); 34 | 35 | // Assert 36 | expect(result).toEqual(testOutput); 37 | }); 38 | }); 39 | 40 | describe('Given sanitize.array is set to false', () => { 41 | it('should return array json values as it is', () => { 42 | // Arrange 43 | const testOutput = { 44 | ...SANITIZE_OUTPUT, 45 | array: SANITIZE_INPUT.EXAMPLE_ARRAY, 46 | }; 47 | 48 | // Act 49 | const result = readEnv('EXAMPLE', { 50 | source: SANITIZE_INPUT, 51 | sanitize: { 52 | array: false, 53 | }, 54 | }); 55 | 56 | // Assert 57 | expect(result).toEqual(testOutput); 58 | }); 59 | }); 60 | 61 | describe('Given sanitize.int is set to false', () => { 62 | it('should return integer values as it is', () => { 63 | // Arrange 64 | const testOutput = { 65 | ...SANITIZE_OUTPUT, 66 | int: SANITIZE_INPUT.EXAMPLE_INT, 67 | negativeInt: SANITIZE_INPUT.EXAMPLE_NEGATIVE_INT, 68 | intZero: SANITIZE_INPUT.EXAMPLE_INT_ZERO, 69 | negativeIntZero: SANITIZE_INPUT.EXAMPLE_NEGATIVE_INT_ZERO, 70 | }; 71 | 72 | // Act 73 | const result = readEnv('EXAMPLE', { 74 | source: SANITIZE_INPUT, 75 | sanitize: { 76 | int: false, 77 | }, 78 | }); 79 | 80 | // Assert 81 | expect(result).toEqual(testOutput); 82 | }); 83 | }); 84 | 85 | describe('Given sanitize.float is set to false', () => { 86 | it('should return float values as it is', () => { 87 | // Arrange 88 | const testOutput = { 89 | ...SANITIZE_OUTPUT, 90 | float: SANITIZE_INPUT.EXAMPLE_FLOAT, 91 | negativeFloat: SANITIZE_INPUT.EXAMPLE_NEGATIVE_FLOAT, 92 | floatZero: SANITIZE_INPUT.EXAMPLE_FLOAT_ZERO, 93 | negativeFloatZero: SANITIZE_INPUT.EXAMPLE_NEGATIVE_FLOAT_ZERO, 94 | }; 95 | 96 | // Act 97 | const result = readEnv('EXAMPLE', { 98 | source: SANITIZE_INPUT, 99 | sanitize: { 100 | float: false, 101 | }, 102 | }); 103 | 104 | // Assert 105 | expect(result).toEqual(testOutput); 106 | }); 107 | }); 108 | 109 | describe('Given sanitize.bool is set to false', () => { 110 | it('should return boolean values as it is', () => { 111 | // Arrange 112 | const testOutput = { 113 | ...SANITIZE_OUTPUT, 114 | true: SANITIZE_INPUT.EXAMPLE_TRUE, 115 | false: SANITIZE_INPUT.EXAMPLE_FALSE, 116 | }; 117 | 118 | // Act 119 | const result = readEnv('EXAMPLE', { 120 | source: SANITIZE_INPUT, 121 | sanitize: { 122 | bool: false, 123 | }, 124 | }); 125 | 126 | // Assert 127 | expect(result).toEqual(testOutput); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/specs/format/format.spec.ts: -------------------------------------------------------------------------------- 1 | import { readEnv } from '../../../src'; 2 | import { FORMAT_INPUT, FORMAT_OUTPUT, ucFirst } from '../../test-utils'; 3 | 4 | describe('Given format is set to false', () => { 5 | it('should return all the keys as it is', () => { 6 | // Act 7 | const result = readEnv('EXAMPLE', { 8 | source: FORMAT_INPUT, 9 | sanitize: false, 10 | format: false, 11 | includePrefix: true, 12 | }); 13 | 14 | // Assert 15 | expect(result).toEqual(FORMAT_INPUT); 16 | }); 17 | }); 18 | 19 | describe('Given format is "camelcase"', () => { 20 | it('should format keys camelcase style', () => { 21 | // Act 22 | const result = readEnv('EXAMPLE', { 23 | source: FORMAT_INPUT, 24 | format: 'camelcase', 25 | }); 26 | 27 | // Assert 28 | expect(result).toEqual(FORMAT_OUTPUT); 29 | }); 30 | }); 31 | 32 | describe('Given format is "pascalcase"', () => { 33 | it('should format keys pascalcase style', () => { 34 | // Arrange 35 | const testInput = { 36 | EXAMPLE_MY_KEY1: 'dummyValue', 37 | EXAMPLE_MY_KEY2: 'dummyValue', 38 | EXAMPLE_MY_kEy3: 'dummyValue', 39 | }; 40 | 41 | const testOutput = { 42 | MyKey1: 'dummyValue', 43 | MyKey2: 'dummyValue', 44 | MyKey3: 'dummyValue', 45 | }; 46 | // Act 47 | const result = readEnv('EXAMPLE', { 48 | source: testInput, 49 | format: 'pascalcase', 50 | }); 51 | 52 | // Assert 53 | expect(result).toEqual(testOutput); 54 | }); 55 | }); 56 | 57 | describe('Given format is "lowercase"', () => { 58 | it('should format keys lowercase style', () => { 59 | // Arrange 60 | const testInput = { 61 | EXAMPLE_MY_KEY1: 'dummyValue', 62 | EXAMPLE_MY_KEY2: 'dummyValue', 63 | }; 64 | 65 | const testOutput = { 66 | my_key1: 'dummyValue', 67 | my_key2: 'dummyValue', 68 | }; 69 | // Act 70 | const result = readEnv('EXAMPLE', { 71 | source: testInput, 72 | format: 'lowercase', 73 | }); 74 | 75 | // Assert 76 | expect(result).toEqual(testOutput); 77 | }); 78 | }); 79 | 80 | describe('Given format is "uppercase"', () => { 81 | it('should format keys uppercase style', () => { 82 | // Arrange 83 | const testInput = { 84 | EXAMPLE_mY_key1: 'dummyValue', 85 | EXAMPLE_MY_KEY2: 'dummyValue', 86 | }; 87 | 88 | const testOutput = { 89 | MY_KEY1: 'dummyValue', 90 | MY_KEY2: 'dummyValue', 91 | }; 92 | // Act 93 | const result = readEnv('EXAMPLE', { 94 | source: testInput, 95 | format: 'uppercase', 96 | }); 97 | 98 | // Assert 99 | expect(result).toEqual(testOutput); 100 | }); 101 | }); 102 | 103 | describe('Given format is custom function', () => { 104 | it('should return all the keys as it is', () => { 105 | // Arrange 106 | const testInput = { 107 | EXAMPLE_mY_key1: 'dummyValue', 108 | EXAMPLE_MY_KEY2: 'dummyValue', 109 | }; 110 | 111 | const testOutput = { 112 | My_key1: 'dummyValue', 113 | My_key2: 'dummyValue', 114 | }; 115 | // Act 116 | const result = readEnv('EXAMPLE', { 117 | source: testInput, 118 | format: ucFirst, 119 | }); 120 | 121 | // Assert 122 | expect(result).toEqual(testOutput); 123 | }); 124 | }); 125 | 126 | describe('Given input contains nested object', () => { 127 | it('should format all nested keys', () => { 128 | // Arrange 129 | 130 | const testInput = { 131 | EXAMPLE_my_key1__my_sub_key1__my_sub_sub_key1: 'dummyValue1', 132 | EXAMPLE_MY_KEY2__my_sub_key2__my_sub_sub_key2: 'dummyValue2', 133 | }; 134 | 135 | const testOutput = { 136 | myKey1: { 137 | mySubKey1: { 138 | mySubSubKey1: 'dummyValue1', 139 | }, 140 | }, 141 | myKey2: { 142 | mySubKey2: { 143 | mySubSubKey2: 'dummyValue2', 144 | }, 145 | }, 146 | }; 147 | // Act 148 | const result = readEnv('EXAMPLE', { 149 | source: testInput, 150 | format: 'camelcase', 151 | }); 152 | 153 | // Assert 154 | expect(result).toEqual(testOutput); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/specs/separator/separator.spec.ts: -------------------------------------------------------------------------------- 1 | import { readEnv } from '../../../src'; 2 | 3 | describe('Given env variable name matches nested object notation', () => { 4 | it('should split keys by separator and create a nested object', () => { 5 | // Arrange 6 | 7 | const testInput = { 8 | EXAMPLE_my_key1__my_sub_key1__my_sub_sub_key1: 'dummyValue1', 9 | EXAMPLE_MY_KEY2__my_sub_key2__my_sub_sub_key2: 'dummyValue2', 10 | }; 11 | 12 | const testOutput = { 13 | myKey1: { 14 | mySubKey1: { 15 | mySubSubKey1: 'dummyValue1', 16 | }, 17 | }, 18 | myKey2: { 19 | mySubKey2: { 20 | mySubSubKey2: 'dummyValue2', 21 | }, 22 | }, 23 | }; 24 | // Act 25 | const result = readEnv('EXAMPLE', { 26 | source: testInput, 27 | }); 28 | 29 | // Assert 30 | expect(result).toEqual(testOutput); 31 | }); 32 | }); 33 | 34 | describe('Given separator is a custom string', () => { 35 | it('should format all nested object property names', () => { 36 | // Arrange 37 | 38 | const testInput = { 39 | EXAMPLE_my_key1$$my_sub_key1$$my_sub_sub_key1: 'dummyValue1', 40 | EXAMPLE_MY_KEY2$$my_sub_key2$$my_sub_sub_key2: 'dummyValue2', 41 | }; 42 | 43 | const testOutput = { 44 | my_key1: { 45 | my_sub_key1: { 46 | my_sub_sub_key1: 'dummyValue1', 47 | }, 48 | }, 49 | my_key2: { 50 | my_sub_key2: { 51 | my_sub_sub_key2: 'dummyValue2', 52 | }, 53 | }, 54 | }; 55 | // Act 56 | const result = readEnv('EXAMPLE', { 57 | source: testInput, 58 | format: 'lowercase', 59 | separator: '$$', 60 | }); 61 | 62 | // Assert 63 | expect(result).toEqual(testOutput); 64 | }); 65 | }); 66 | 67 | describe('Given separator is set to "false"', () => { 68 | it('should NOT create nested objects', () => { 69 | // Arrange 70 | 71 | const testInput = { 72 | EXAMPLE_x: 'willNotBeOverwritten', 73 | EXAMPLE_x__y__z: 'dummyValue1', 74 | EXAMPLE_x__a__b: 'dummyValue2', 75 | }; 76 | 77 | const testOutput = { 78 | x: 'willNotBeOverwritten', 79 | xYZ: 'dummyValue1', 80 | xAB: 'dummyValue2', 81 | }; 82 | // Act 83 | const result = readEnv('EXAMPLE', { 84 | source: testInput, 85 | separator: false, 86 | }); 87 | 88 | // Assert 89 | expect(result).toEqual(testOutput); 90 | }); 91 | }); 92 | 93 | describe('Given some nested objects belong to same parents', () => { 94 | it('should merge child objects', () => { 95 | // Arrange 96 | 97 | const testInput = { 98 | EXAMPLE_x: 'willBeOverwritten', 99 | EXAMPLE_x__y__z: 'dummyValue1', 100 | EXAMPLE_x__a__b: 'dummyValue2', 101 | }; 102 | 103 | const testOutput = { 104 | x: { 105 | y: { 106 | z: 'dummyValue1', 107 | }, 108 | a: { 109 | b: 'dummyValue2', 110 | }, 111 | }, 112 | }; 113 | // Act 114 | const result = readEnv('EXAMPLE', { 115 | source: testInput, 116 | }); 117 | 118 | // Assert 119 | expect(result).toEqual(testOutput); 120 | }); 121 | }); 122 | 123 | describe('Given a parent key was defined previously', () => { 124 | it('should overwrite the parent that is not object', () => { 125 | // Arrange 126 | const testInput = { 127 | // String 128 | EXAMPLE_x1: 'willBeOverwritten', 129 | EXAMPLE_x1__y__z: 'dummyValue1', 130 | // Array 131 | EXAMPLE_x2: '["dummyValue2"]', 132 | EXAMPLE_x2__a__b: 'dummyValue2', 133 | 134 | // Object 135 | EXAMPLE_x3: '{"will": "keep"}', 136 | EXAMPLE_x3__k__l: 'dummyValue3', 137 | }; 138 | 139 | const testOutput = { 140 | x1: { 141 | y: { 142 | z: 'dummyValue1', 143 | }, 144 | }, 145 | x2: { 146 | a: { 147 | b: 'dummyValue2', 148 | }, 149 | }, 150 | x3: { 151 | will: 'keep', 152 | k: { l: 'dummyValue3' }, 153 | }, 154 | }; 155 | 156 | // Act 157 | const result = readEnv('EXAMPLE', { 158 | source: testInput, 159 | }); 160 | 161 | // Assert 162 | expect(result).toEqual(testOutput); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # read-env 2 | 3 | > Transform environment variables into JSON object with sanitized values. 4 | 5 | [![NPM version](https://badge.fury.io/js/read-env.svg)](https://www.npmjs.com/package/read-env) 6 | [![npm](https://img.shields.io/npm/dt/read-env.svg)](https://www.npmjs.com/package/read-env) 7 | [![Coverage Status](https://coveralls.io/repos/github/yatki/read-env/badge.svg?branch=master&)](https://coveralls.io/github/yatki/read-env?branch=master) 8 | [![Dependencies](https://david-dm.org/yatki/read-env/status.svg)](https://david-dm.org/yatki/read-env) 9 | 10 | > See docs for previous version [v1.3.x](https://github.com/yatki/read-env/tree/v1.3.0). 11 | 12 | Main purpose of this library is to allow developers to configure their applications with environment variables. See: [a use case example](#use-case-example). 13 | 14 | ## What's New with v2.x 🚀 15 | 16 | - Migrated to Typescript, Yay! 🎉 17 | - Simplified API 18 | - With new `separator` option,nested object constructions are possible. 19 | - New `source` option allows you to use other objects, other than `process.env` 20 | 21 | ## Migrating from v1.x to v2.x 22 | 23 | - `default` export is **deprecated**. Please use named export `readEnv` as below: 24 | 25 | ```js 26 | const { readEnv } = require('read-env'); 27 | // Or 28 | import { readEnv } from 'read-env'; 29 | // Or in browser 30 | window.readEnv('EXAMPLE'); 31 | ``` 32 | 33 | - `parse` option was renamed as `sanitize`. 34 | - `transformKey` option was renamed as `format`. 35 | - Deprecated options: `ignoreInvalidJSON`, `prefix`, `filter`, 36 | 37 | ## Install 38 | 39 | ``` 40 | npm install --save read-env 41 | ``` 42 | 43 | or 44 | 45 | ``` 46 | yarn add read-env 47 | ``` 48 | 49 | ## Basic Usage 50 | 51 | Let's say you have some environment variables starting with prefix "**EXAMPLE\_**" like below: 52 | 53 | ```text 54 | EXAMPLE_OBJECT='{"prop": "value"}' 55 | EXAMPLE_ARRAY='[1,2,3, "string", {"prop": "value"}, 5.2]' 56 | EXAMPLE_INVALID_OBJECT='{"prop": }"value"}' 57 | EXAMPLE_INVALID_ARRAY='[1,2,3, "string", ]{"prop": "value"}, 5.2]' 58 | EXAMPLE_TRUE='true' 59 | EXAMPLE_FALSE='false' 60 | EXAMPLE_INT='5' 61 | EXAMPLE_NEGATIVE_INT='-11' 62 | EXAMPLE_FLOAT='5.2456' 63 | EXAMPLE_NEGATIVE_FLOAT='-2.4567' 64 | EXAMPLE_INT_ZERO='0' 65 | EXAMPLE_FLOAT_ZERO='0.00' 66 | EXAMPLE_NEGATIVE_INT_ZERO='-0' 67 | EXAMPLE_NEGATIVE_FLOAT_ZERO='-0.00' 68 | EXAMPLE_STRING='example' 69 | EXAMPLE_DEEP__OBJECT__PROPERTY='value' 70 | ``` 71 | 72 | app.js 73 | 74 | ```js 75 | import { readEnv } from 'read-env'; 76 | 77 | const result = readEnv('EXAMPLE'); 78 | console.log(result); 79 | ``` 80 | 81 | Result: 82 | 83 | ```json 84 | { 85 | "object": { "prop": "value" }, 86 | "array": [1, 2, 3, "string", { "prop": "value" }, 5.2], 87 | "invalidObject": "{\"prop\": }\"value\"}", 88 | "invalidArray": "[1,2,3, \"string\", ]{\"prop\": \"value\"}, 5.2]", 89 | "true": true, 90 | "false": false, 91 | "int": 5, 92 | "negativeInt": -11, 93 | "float": 5.2456, 94 | "negativeFloat": -2.4567, 95 | "intZero": 0, 96 | "floatZero": 0, 97 | "negativeIntZero": -0, 98 | "negativeFloatZero": -0, 99 | "string": "example", 100 | "deep": { 101 | "object": { 102 | "property": "value" 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | ## API 109 | 110 | ### `readEnv(prefix?: string, options: ReadEnvOptions = {})` 111 | 112 | **Input:** 113 | 114 | - `prefix` (type: `string`, default: `undefined`): filters environment variables by prefix 115 | - `options` (type: `ReadEnvOptions`, default: `{}`): options object to change function's behaviour 116 | 117 | **Returns:** `object` (type: _Record_), returns the instance, so add methods are chainable. 118 | 119 | ### Options 120 | 121 | **Default Options:** 122 | 123 | ```js 124 | { 125 | "source": process.env, 126 | "format": "camelcase", 127 | "separator": "__", 128 | "sanitize": { 129 | "object": true, 130 | "array": true, 131 | "bool": true, 132 | "int": true, 133 | "float": true 134 | }, 135 | "includePrefix": false 136 | } 137 | ``` 138 | 139 | - [`options.source`](#optionssource) 140 | - [`options.format`](#optionsformat) 141 | - [`options.separator`](#optionsseparator) 142 | - [`options.sanitize`](#optionssanitize) 143 | - [`options.includePrefix`](#optionsincludeprefix) 144 | 145 | #### `options.source` 146 | 147 | - Type: `object` 148 | - Default: `process.env` 149 | 150 | The source object that will be filtered, sanitized and formatted. 151 | 152 | **Type Signature:** 153 | 154 | ```ts 155 | interface Source { 156 | [key: string]: string | undefined; 157 | } 158 | ``` 159 | 160 | #### `options.format` 161 | 162 | - Type: `boolean | string | function` 163 | - Default: `camelcase` 164 | 165 | Format environment variable name. 166 | 167 | It's value can be: 168 | 169 | - a `boolean`, if set to `false`, formatting is disabled 170 | - a `string`, one of which `camelcase`, `pascalcase`, `lowercase`, `uppercase` 171 | - a `function`, with `(rawVarName: string) => string` type signature 172 | 173 | #### `options.separator` 174 | 175 | - Type: `boolean | string` 176 | - Default: `__` 177 | 178 | Allows you construct nested objects from environment variable name. 179 | 180 | - If set to `false`, constructing nested objects is disabled 181 | 182 | **Example:** 183 | 184 | ```js 185 | const { readEnv } = require('read-env'); 186 | 187 | const testInput = { 188 | EXAMPLE_DEEP__OBJECT_PROPERTY1: 'value1', 189 | EXAMPLE_DEEP__OBJECT_PROPERTY2: 'value2', 190 | }; 191 | 192 | const result = readEnv('EXAMPLE', { 193 | source: testInput, 194 | separator: '_', 195 | }); 196 | console.log(result); 197 | ``` 198 | 199 | Result: 200 | 201 | ```json 202 | { 203 | "deep": { 204 | "object": { 205 | "property1": "value1", 206 | "property2": "value2" 207 | } 208 | } 209 | } 210 | ``` 211 | 212 | #### `options.sanitize` 213 | 214 | - Type: `boolean | object`, 215 | - Default: `{}` 216 | 217 | Sanitize object consists of following properties which is used to 218 | 219 | - `object` (type: _bool_, default: _true_): sanitize stringified object 220 | 221 | > value must be valid JSON input, see: [JSON.parse](). 222 | 223 | - `array` (type: _bool_, default: _true_): sanitize stringified array 224 | > value must be valid JSON input, see: [JSON.parse](). 225 | - `int` (type: _bool_, default: _true_): sanitize numbers into integer 226 | > value must be consist of only digits. 227 | - `float` (type: _bool_, default: _true_): sanitize numbers into float 228 | > value must be consist of only digits with decimal point. 229 | - `bool` (type: _bool_, default: _true_): sanitize value into boolean 230 | > value must have case insensitive match with "true" or "false". 231 | 232 | #### `options.includePrefix` 233 | 234 | - Type: `boolean` 235 | - Default: `false` 236 | 237 | If set to true, keeps the given prefix in property names. 238 | 239 | ## Use Case Example 240 | 241 | In past, I used [Nightmare](https://github.com/segmentio/nightmare) for _acceptance testing_ and tests had different configurations based on the 242 | environment they were running on. 243 | 244 | So, I simply used read-env, and nightmare is fully configurable with environment variables :) 245 | 246 | ```js 247 | import Nightmare from 'nightmare'; 248 | import { readEnv } from 'read-env'; 249 | 250 | const nightmareConfig = readEnv('MY_NIGHTMARE'); 251 | const nightmare = Nightmare(nightmareConfig); 252 | ``` 253 | 254 | Instead of writing code like below: 255 | 256 | ```js 257 | import Nightmare from 'nightmare'; 258 | 259 | const nightmare = Nightmare({ 260 | show: process.env.MY_NIGHTMARE_SHOW || false, 261 | width: process.env.MY_NIGHTMARE_WIDTH || 1280, 262 | height: process.env.MY_NIGHTMARE_HEIGHT || 720, 263 | typeInterval: process.env.MY_NIGHTMARE_TYPE_INTERVAL || 50, 264 | //... other properties go forever 265 | }); 266 | ``` 267 | 268 | ## Contribution 269 | 270 | As always, I'm open to any contribution and would like to hear your feedback. 271 | 272 | ### Just an important reminder: 273 | 274 | If you are planning to contribute to **any** open source project, 275 | before starting development, please **always open an issue** and **make a proposal first**. 276 | This will save you from working on features that are eventually going to be rejected for some reason. 277 | 278 | ## LICENCE 279 | 280 | MIT (c) 2020 Mehmet Yatkı 281 | --------------------------------------------------------------------------------