Skip to content

Commit

Permalink
Merge pull request #784 from motdotla/multiple-files
Browse files Browse the repository at this point in the history
README update for multi-file support
  • Loading branch information
motdotla committed Jan 23, 2024
2 parents 74d8199 + 687c4df commit 58cb202
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 10 deletions.
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -190,6 +190,8 @@ You need to deploy your secrets in a cloud-agnostic manner? Use a `.env.vault` f

## 🌴 Manage Multiple Environments

Use [dotenv-vault](https://github.com/dotenv-org/dotenv-vault).

Edit your production environment variables.

```bash
Expand Down Expand Up @@ -296,6 +298,12 @@ Specify a custom path if your file containing environment variables is located e
require('dotenv').config({ path: '/custom/path/to/.env' })
```

By default, `config` will look for a file called .env in the current working directory. Pass in multiple files as an array, and they will be loaded in order. The first value set for a variable will win.

```js
require('dotenv').config({ path: ['.env.local', '.env'] })
```

##### encoding

Default: `utf8`
Expand Down
6 changes: 4 additions & 2 deletions lib/main.d.ts
@@ -1,6 +1,6 @@
// TypeScript Version: 3.0
/// <reference types="node" />
import type { URL } from 'node:url';
import type { URL } from 'url';

export interface DotenvParseOutput {
[name: string]: string;
Expand All @@ -23,10 +23,12 @@ export interface DotenvConfigOptions {
* Default: `path.resolve(process.cwd(), '.env')`
*
* Specify a custom path if your file containing environment variables is located elsewhere.
* Can also be an array of strings, specifying multiple paths.
*
* example: `require('dotenv').config({ path: '/custom/path/to/.env' })`
* example: `require('dotenv').config({ path: ['/path/to/first.env', '/path/to/second.env'] })`
*/
path?: string | URL;
path?: string | string[] | URL;

/**
* Default: `utf8`
Expand Down
27 changes: 20 additions & 7 deletions lib/main.js
Expand Up @@ -160,14 +160,27 @@ function _instructions (result, dotenvKey) {
}

function _vaultPath (options) {
let dotenvPath = path.resolve(process.cwd(), '.env')
let possibleVaultPath = null

if (options && options.path && options.path.length > 0) {
dotenvPath = options.path
if (Array.isArray(options.path)) {
for (const filepath of options.path) {
if (fs.existsSync(filepath)) {
possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`
}
}
} else {
possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`
}
} else {
possibleVaultPath = path.resolve(process.cwd(), '.env.vault')
}

// Locate .env.vault
return dotenvPath.endsWith('.vault') ? dotenvPath : `${dotenvPath}.vault`
if (fs.existsSync(possibleVaultPath)) {
return possibleVaultPath
}

return null
}

function _resolveHome (envPath) {
Expand Down Expand Up @@ -230,15 +243,15 @@ function configDotenv (options) {

// Populates process.env from .env file
function config (options) {
const vaultPath = _vaultPath(options)

// fallback to original dotenv if DOTENV_KEY is not set
if (_dotenvKey(options).length === 0) {
return DotenvModule.configDotenv(options)
}

const vaultPath = _vaultPath(options)

// dotenvKey exists but .env.vault file does not exist
if (!fs.existsSync(vaultPath)) {
if (!vaultPath) {
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)

return DotenvModule.configDotenv(options)
Expand Down
2 changes: 2 additions & 0 deletions tests/.env.local
@@ -0,0 +1,2 @@
BASIC=local_basic
LOCAL=local
42 changes: 41 additions & 1 deletion tests/test-config-vault.js
Expand Up @@ -25,6 +25,15 @@ t.afterEach(() => {
}
})

t.test('logs when no path is set', ct => {
ct.plan(1)

logStub = sinon.stub(console, 'log')

dotenv.config()
ct.ok(logStub.called)
})

t.test('logs', ct => {
ct.plan(1)

Expand All @@ -43,7 +52,7 @@ t.test('logs when testPath calls to .env.vault directly (interpret what the user
ct.ok(logStub.called)
})

t.test('warns if DOTENV_KEY exists but .env.vault does not', ct => {
t.test('warns if DOTENV_KEY exists but .env.vault does not exist', ct => {
ct.plan(1)

logStub = sinon.stub(console, 'log')
Expand All @@ -56,6 +65,19 @@ t.test('warns if DOTENV_KEY exists but .env.vault does not', ct => {
ct.end()
})

t.test('warns if DOTENV_KEY exists but .env.vault does not exist (set as array)', ct => {
ct.plan(1)

logStub = sinon.stub(console, 'log')

const existsSync = sinon.stub(fs, 'existsSync').returns(false) // make .env.vault not exist
dotenv.config({ path: [testPath] })
ct.ok(logStub.called)
existsSync.restore()

ct.end()
})

t.test('returns parsed object', ct => {
ct.plan(1)

Expand All @@ -65,6 +87,24 @@ t.test('returns parsed object', ct => {
ct.end()
})

t.test('returns parsed object (set path as array)', ct => {
ct.plan(1)

const env = dotenv.config({ path: [testPath] })
ct.same(env.parsed, { ALPHA: 'zeta' })

ct.end()
})

t.test('returns parsed object (set path as array with .vault extension)', ct => {
ct.plan(1)

const env = dotenv.config({ path: [`${testPath}.vault`] })
ct.same(env.parsed, { ALPHA: 'zeta' })

ct.end()
})

t.test('throws not found if .env.vault is empty', ct => {
ct.plan(2)

Expand Down
27 changes: 27 additions & 0 deletions tests/test-config.js
Expand Up @@ -30,6 +30,33 @@ t.test('takes string for path option', ct => {
ct.equal(readFileSyncStub.args[0][0], testPath)
})

t.test('takes string for path option', ct => {
ct.plan(1)

const testPath = 'tests/.env'
dotenv.config({ path: testPath })

ct.equal(readFileSyncStub.args[0][0], testPath)
})

t.test('takes array for path option', ct => {
ct.plan(1)

const testPath = ['tests/.env']
dotenv.config({ path: testPath })

ct.equal(readFileSyncStub.args[0][0], testPath)
})

t.test('takes two or more files in the array for path option', ct => {
ct.plan(1)

const testPath = ['tests/.env.local', 'tests/.env']
dotenv.config({ path: testPath })

ct.equal(readFileSyncStub.args[0][0], testPath)
})

t.test('takes URL for path option', ct => {
ct.plan(1)

Expand Down

0 comments on commit 58cb202

Please sign in to comment.