| 1 | /* @flow */
|
|---|
| 2 | /*::
|
|---|
| 3 |
|
|---|
| 4 | type DotenvParseOptions = {
|
|---|
| 5 | debug?: boolean
|
|---|
| 6 | }
|
|---|
| 7 |
|
|---|
| 8 | // keys and values from src
|
|---|
| 9 | type DotenvParseOutput = { [string]: string }
|
|---|
| 10 |
|
|---|
| 11 | type DotenvConfigOptions = {
|
|---|
| 12 | path?: string, // path to .env file
|
|---|
| 13 | encoding?: string, // encoding of .env file
|
|---|
| 14 | debug?: string // turn on logging for debugging purposes
|
|---|
| 15 | }
|
|---|
| 16 |
|
|---|
| 17 | type DotenvConfigOutput = {
|
|---|
| 18 | parsed?: DotenvParseOutput,
|
|---|
| 19 | error?: Error
|
|---|
| 20 | }
|
|---|
| 21 |
|
|---|
| 22 | */
|
|---|
| 23 |
|
|---|
| 24 | const fs = require('fs')
|
|---|
| 25 | const path = require('path')
|
|---|
| 26 | const os = require('os')
|
|---|
| 27 |
|
|---|
| 28 | function log (message /*: string */) {
|
|---|
| 29 | console.log(`[dotenv][DEBUG] ${message}`)
|
|---|
| 30 | }
|
|---|
| 31 |
|
|---|
| 32 | const NEWLINE = '\n'
|
|---|
| 33 | const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/
|
|---|
| 34 | const RE_NEWLINES = /\\n/g
|
|---|
| 35 | const NEWLINES_MATCH = /\r\n|\n|\r/
|
|---|
| 36 |
|
|---|
| 37 | // Parses src into an Object
|
|---|
| 38 | function parse (src /*: string | Buffer */, options /*: ?DotenvParseOptions */) /*: DotenvParseOutput */ {
|
|---|
| 39 | const debug = Boolean(options && options.debug)
|
|---|
| 40 | const obj = {}
|
|---|
| 41 |
|
|---|
| 42 | // convert Buffers before splitting into lines and processing
|
|---|
| 43 | src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
|
|---|
| 44 | // matching "KEY' and 'VAL' in 'KEY=VAL'
|
|---|
| 45 | const keyValueArr = line.match(RE_INI_KEY_VAL)
|
|---|
| 46 | // matched?
|
|---|
| 47 | if (keyValueArr != null) {
|
|---|
| 48 | const key = keyValueArr[1]
|
|---|
| 49 | // default undefined or missing values to empty string
|
|---|
| 50 | let val = (keyValueArr[2] || '')
|
|---|
| 51 | const end = val.length - 1
|
|---|
| 52 | const isDoubleQuoted = val[0] === '"' && val[end] === '"'
|
|---|
| 53 | const isSingleQuoted = val[0] === "'" && val[end] === "'"
|
|---|
| 54 |
|
|---|
| 55 | // if single or double quoted, remove quotes
|
|---|
| 56 | if (isSingleQuoted || isDoubleQuoted) {
|
|---|
| 57 | val = val.substring(1, end)
|
|---|
| 58 |
|
|---|
| 59 | // if double quoted, expand newlines
|
|---|
| 60 | if (isDoubleQuoted) {
|
|---|
| 61 | val = val.replace(RE_NEWLINES, NEWLINE)
|
|---|
| 62 | }
|
|---|
| 63 | } else {
|
|---|
| 64 | // remove surrounding whitespace
|
|---|
| 65 | val = val.trim()
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | obj[key] = val
|
|---|
| 69 | } else if (debug) {
|
|---|
| 70 | log(`did not match key and value when parsing line ${idx + 1}: ${line}`)
|
|---|
| 71 | }
|
|---|
| 72 | })
|
|---|
| 73 |
|
|---|
| 74 | return obj
|
|---|
| 75 | }
|
|---|
| 76 |
|
|---|
| 77 | function resolveHome (envPath) {
|
|---|
| 78 | return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
|
|---|
| 79 | }
|
|---|
| 80 |
|
|---|
| 81 | // Populates process.env from .env file
|
|---|
| 82 | function config (options /*: ?DotenvConfigOptions */) /*: DotenvConfigOutput */ {
|
|---|
| 83 | let dotenvPath = path.resolve(process.cwd(), '.env')
|
|---|
| 84 | let encoding /*: string */ = 'utf8'
|
|---|
| 85 | let debug = false
|
|---|
| 86 |
|
|---|
| 87 | if (options) {
|
|---|
| 88 | if (options.path != null) {
|
|---|
| 89 | dotenvPath = resolveHome(options.path)
|
|---|
| 90 | }
|
|---|
| 91 | if (options.encoding != null) {
|
|---|
| 92 | encoding = options.encoding
|
|---|
| 93 | }
|
|---|
| 94 | if (options.debug != null) {
|
|---|
| 95 | debug = true
|
|---|
| 96 | }
|
|---|
| 97 | }
|
|---|
| 98 |
|
|---|
| 99 | try {
|
|---|
| 100 | // specifying an encoding returns a string instead of a buffer
|
|---|
| 101 | const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })
|
|---|
| 102 |
|
|---|
| 103 | Object.keys(parsed).forEach(function (key) {
|
|---|
| 104 | if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
|
|---|
| 105 | process.env[key] = parsed[key]
|
|---|
| 106 | } else if (debug) {
|
|---|
| 107 | log(`"${key}" is already defined in \`process.env\` and will not be overwritten`)
|
|---|
| 108 | }
|
|---|
| 109 | })
|
|---|
| 110 |
|
|---|
| 111 | return { parsed }
|
|---|
| 112 | } catch (e) {
|
|---|
| 113 | return { error: e }
|
|---|
| 114 | }
|
|---|
| 115 | }
|
|---|
| 116 |
|
|---|
| 117 | module.exports.config = config
|
|---|
| 118 | module.exports.parse = parse
|
|---|