Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

README update for multi-file support #784

Merged
merged 4 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,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 @@ -286,6 +288,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
Original file line number Diff line number Diff line change
@@ -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 @@ -24,10 +24,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
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,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 @@ -216,15 +229,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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BASIC=local_basic
LOCAL=local
42 changes: 41 additions & 1 deletion tests/test-config-vault.js
Original file line number Diff line number Diff line change
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(1)

Expand Down
27 changes: 27 additions & 0 deletions tests/test-config.js
Original file line number Diff line number Diff line change
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