Skip to content

Commit

Permalink
Fix merge conflicts for older PR
Browse files Browse the repository at this point in the history
  • Loading branch information
motdotla committed Jan 18, 2022
2 parents 44281f4 + 9b1d338 commit 8875300
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 13 deletions.
55 changes: 55 additions & 0 deletions README.md
Expand Up @@ -151,6 +151,7 @@ Turn on logging to help debug why certain keys or values are not being set as yo
require('dotenv').config({ debug: process.env.DEBUG })
```

<<<<<<< HEAD
##### Override

Default: `false`
Expand All @@ -162,6 +163,32 @@ require('dotenv').config({ override: true })
```

### Parse
=======
#### Multiline

Default: `default`

You may specify the value `line-breaks` to switch the parser into a mode in which line breaks
inside quoted values are allowed.

```js
require('dotenv').config({ multiline: 'line-breaks' })
```

This allows specifying multiline values in this format:

```
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGT...
7ure...
-----END PRIVATE KEY-----"
```

Ensure that the value begins with a single or double quote character, and it ends with the same character.


## Parse
>>>>>>> 9b1d338e76daa73fa4fb8ed27b94082d80310eba
The engine which parses the contents of your file containing environment
variables is available to use. It accepts a String or Buffer and will return
Expand Down Expand Up @@ -194,6 +221,7 @@ const config = dotenv.parse(buf, opt)

### Preload

<<<<<<< HEAD
You can use the `--require` (`-r`) [command line option](https://nodejs.org/api/cli.html#cli_r_require_module) to preload dotenv. By doing this, you do not need to require and load dotenv in your application code. This is the preferred approach when using `import` instead of `require`.

```bash
Expand All @@ -215,6 +243,33 @@ $ DOTENV_CONFIG_<OPTION>=value node -r dotenv/config your_script.js
```bash
$ DOTENV_CONFIG_ENCODING=latin1 DOTENV_CONFIG_DEBUG=true node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env
```
=======
- `BASIC=basic` becomes `{BASIC: 'basic'}`
- empty lines are skipped
- lines beginning with `#` are treated as comments
- empty values become empty strings (`EMPTY=` becomes `{EMPTY: ''}`)
- inner quotes are maintained (think JSON) (`JSON={"foo": "bar"}` becomes `{JSON:"{\"foo\": \"bar\"}"`)
- whitespace is removed from both ends of unquoted values (see more on [`trim`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)) (`FOO= some value ` becomes `{FOO: 'some value'}`)
- single and double quoted values are escaped (`SINGLE_QUOTE='quoted'` becomes `{SINGLE_QUOTE: "quoted"}`)
- single and double quoted values maintain whitespace from both ends (`FOO=" some value "` becomes `{FOO: ' some value '}`)
- double quoted values expand new lines. Example: `MULTILINE="new\nline"` becomes

```
{MULTILINE: 'new
line'}
```
- multi-line values with line breaks are supported for quoted values if using the `{ multiline: "line-break" }` option.
In this mode you do not need to use `\n` to separate lines. Example:

```
PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGT...
7ure...
-----END PRIVATE KEY-----"
```

Note that when using this option, all values that start with quotes must end in quotes.
>>>>>>> 9b1d338e76daa73fa4fb8ed27b94082d80310eba
## FAQ

Expand Down
2 changes: 1 addition & 1 deletion lib/cli-options.js
@@ -1,4 +1,4 @@
const re = /^dotenv_config_(encoding|path|debug|override)=(.+)$/
const re = /^dotenv_config_(encoding|path|debug|override|multiline)=(.+)$/

module.exports = function optionMatcher (args) {
return args.reduce(function (acc, cur) {
Expand Down
4 changes: 4 additions & 0 deletions lib/env-options.js
Expand Up @@ -17,4 +17,8 @@ if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
options.override = process.env.DOTENV_CONFIG_OVERRIDE
}

if (process.env.DOTENV_CONFIG_MULTILINE != null) {
options.multiline = process.env.DOTENV_CONFIG_MULTILINE
}

module.exports = options
32 changes: 31 additions & 1 deletion lib/main.d.ts
Expand Up @@ -10,6 +10,21 @@ export interface DotenvParseOptions {
* example: `dotenv.parse('KEY=value', { debug: true })`
*/
debug?: boolean;

/**
* Default: `false`
*
* Turn on multiline line break parsing.
*
* example:
*
* MY_VAR="this
* is
* a
* multiline
* string"
*/
multiline?: boolean;
}

export interface DotenvParseOutput {
Expand Down Expand Up @@ -66,6 +81,21 @@ export interface DotenvConfigOptions {
* example: `require('dotenv').config({ override: true })`
*/
override?: boolean;

/**
* Default: `false`
*
* Turn on multiline line break parsing.
*
* example:
*
* MY_VAR="this
* is
* a
* multiline
* string"
*/
multiline?: boolean;
}

export interface DotenvConfigOutput {
Expand All @@ -78,7 +108,7 @@ export interface DotenvConfigOutput {
*
* See https://docs.dotenv.org
*
* @param options - additional options. example: `{ path: './custom/path', encoding: 'latin1', debug: true, override: false }`
* @param options - additional options. example: `{ path: './custom/path', encoding: 'latin1', debug: true, override: false, multiline: false }`
* @returns an object with a `parsed` key if successful or `error` key if an error occurred. example: { parsed: { KEY: 'value' } }
*
*/
Expand Down
42 changes: 35 additions & 7 deletions lib/main.js
Expand Up @@ -14,23 +14,46 @@ const NEWLINES_MATCH = /\r\n|\n|\r/
// Parses src into an Object
function parse (src, options) {
const debug = Boolean(options && options.debug)
const multiline = Boolean(options && options.multiline)
const obj = {}

// convert Buffers before splitting into lines and processing
src.toString().split(NEWLINES_MATCH).forEach(function (line, idx) {
const lines = src.toString().split(NEWLINES_MATCH)

for (let idx = 0; idx < lines.length; idx++) {
let line = lines[idx]

// matching "KEY' and 'VAL' in 'KEY=VAL'
const keyValueArr = line.match(RE_INI_KEY_VAL)
// matched?
if (keyValueArr != null) {
const key = keyValueArr[1]
// default undefined or missing values to empty string
let val = (keyValueArr[2] || '')
const end = val.length - 1
let end = val.length - 1
const isDoubleQuoted = val[0] === '"' && val[end] === '"'
const isSingleQuoted = val[0] === "'" && val[end] === "'"

const isMultilineDoubleQuoted = val[0] === '"' && val[end] !== '"'
const isMultilineSingleQuoted = val[0] === "'" && val[end] !== "'"

// if parsing line breaks and the value starts with a quote
if (multiline && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
const quoteChar = isMultilineDoubleQuoted ? '"' : "'"

val = val.substring(1)

while (idx++ < lines.length - 1) {
line = lines[idx]
end = line.length - 1
if (line[end] === quoteChar) {
val += NEWLINE + line.substring(0, end)
break
}
val += NEWLINE + line
}
// if single or double quoted, remove quotes
if (isSingleQuoted || isDoubleQuoted) {
} else if (isSingleQuoted || isDoubleQuoted) {
val = val.substring(1, end)

// if double quoted, expand newlines
Expand All @@ -51,7 +74,7 @@ function parse (src, options) {
log(`Failed to match key and value when parsing line ${idx + 1}: ${line}`)
}
}
})
}

return obj
}
Expand All @@ -66,6 +89,7 @@ function config (options) {
let encoding = 'utf8'
const debug = Boolean(options && options.debug)
const override = Boolean(options && options.override)
const multiline = Boolean(options && options.multiline)

if (options) {
if (options.path != null) {
Expand All @@ -78,7 +102,7 @@ function config (options) {

try {
// specifying an encoding returns a string instead of a buffer
const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug })
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }), { debug, multiline })

Object.keys(parsed).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
Expand Down Expand Up @@ -108,5 +132,9 @@ function config (options) {
}
}

module.exports.config = config
module.exports.parse = parse
const DotenvModule = {
config,
parse
}

module.exports = DotenvModule
36 changes: 36 additions & 0 deletions tests/.env-multiline
@@ -0,0 +1,36 @@
BASIC=basic

# previous line intentionally left blank
AFTER_LINE=after_line
EMPTY=
SINGLE_QUOTES='single_quotes'
SINGLE_QUOTES_SPACED=' single quotes '
DOUBLE_QUOTES="double_quotes"
DOUBLE_QUOTES_SPACED=" double quotes "
EXPAND_NEWLINES="expand\nnew\nlines"
DONT_EXPAND_UNQUOTED=dontexpand\nnewlines
DONT_EXPAND_SQUOTED='dontexpand\nnewlines'
# COMMENTS=work
EQUAL_SIGNS=equals==
RETAIN_INNER_QUOTES={"foo": "bar"}

RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}'
TRIM_SPACE_FROM_UNQUOTED= some spaced out string
USERNAME=therealnerdybeast@example.tld
SPACED_KEY = parsed

MULTI_DOUBLE_QUOTED="THIS
IS
A
MULTILINE
STRING"

MULTI_SINGLE_QUOTED='THIS
IS
A
MULTILINE
STRING'

MULTI_UNENDED="THIS
LINE HAS
NO END QUOTE
5 changes: 5 additions & 0 deletions tests/test-cli-options.js
Expand Up @@ -24,6 +24,11 @@ t.same(options(['node', '-e', "'console.log(testing)'", 'dotenv_config_override=
override: 'true'
})

// matches multiline option
t.same(options(['node', '-e', "'console.log(testing)'", 'dotenv_config_multiline=true']), {
multiline: 'true'
})

// ignores empty values
t.same(options(['node', '-e', "'console.log(testing)'", 'dotenv_config_path=']), {})

Expand Down
8 changes: 5 additions & 3 deletions tests/test-config-cli.js
Expand Up @@ -3,6 +3,8 @@ const path = require('path')

const t = require('tap')

const configPath = path.resolve(__dirname, '../config.js')

function spawn (cmd, options = {}) {
const { stdout } = cp.spawnSync(
process.argv[0], // node binary
Expand All @@ -28,7 +30,7 @@ t.equal(
spawn(
[
'-r',
'./config',
configPath,
'-e',
'console.log(process.env.BASIC)',
'dotenv_config_encoding=utf8',
Expand All @@ -43,7 +45,7 @@ t.equal(
spawn(
[
'-r',
'./config',
configPath,
'-e',
'console.log(process.env.BASIC)'
],
Expand All @@ -61,7 +63,7 @@ t.equal(
spawn(
[
'-r',
'./config',
configPath,
'-e',
'console.log(process.env.BASIC)',
'dotenv_config_path=./tests/.env'
Expand Down
7 changes: 7 additions & 0 deletions tests/test-config.js
Expand Up @@ -63,6 +63,13 @@ t.test('takes option for debug', ct => {
logStub.restore()
})

t.test('takes option for multiline', ct => {
ct.plan(1)
const testMultiline = true
dotenv.config({ multiline: testMultiline })
ct.equal(parseStub.args[0][1].multiline, testMultiline)
})

t.test('reads path with encoding, parsing output to process.env', ct => {
ct.plan(2)

Expand Down
6 changes: 6 additions & 0 deletions tests/test-env-options.js
Expand Up @@ -9,6 +9,7 @@ const e = process.env.DOTENV_CONFIG_ENCODING
const p = process.env.DOTENV_CONFIG_PATH
const d = process.env.DOTENV_CONFIG_DEBUG
const o = process.env.DOTENV_CONFIG_OVERRIDE
const m = process.env.DOTENV_CONFIG_MULTILINE

// get fresh object for each test
function options () {
Expand All @@ -32,6 +33,7 @@ delete process.env.DOTENV_CONFIG_ENCODING
delete process.env.DOTENV_CONFIG_PATH
delete process.env.DOTENV_CONFIG_DEBUG
delete process.env.DOTENV_CONFIG_OVERRIDE
delete process.env.DOTENV_CONFIG_MULTILINE

t.same(options(), {})

Expand All @@ -47,8 +49,12 @@ testOption('DOTENV_CONFIG_DEBUG', 'true', { debug: 'true' })
// sets override option
testOption('DOTENV_CONFIG_OVERRIDE', 'true', { override: 'true' })

// sets multiline option
testOption('DOTENV_CONFIG_MULTILINE', 'true', { multiline: 'true' })

// restore existing env
process.env.DOTENV_CONFIG_ENCODING = e
process.env.DOTENV_CONFIG_PATH = p
process.env.DOTENV_CONFIG_DEBUG = d
process.env.DOTENV_CONFIG_OVERRIDE = o
process.env.DOTENV_CONFIG_MULTILINE = m

0 comments on commit 8875300

Please sign in to comment.