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

[FEATURE] apply function added where we can pass target, source and o… #733

Merged
merged 2 commits into from
May 21, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions lib/main.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,34 @@ export interface DotenvConfigOutput {
parsed?: DotenvParseOutput;
}

export interface DotenvApplyOptions {
/**
* Default: `false`
*
* Turn on logging to help debug why certain keys or values are not being set as you expect.
*
* example: `require('dotenv').config({ debug: process.env.DEBUG })`
*/
debug?: boolean;

/**
* Default: `false`
*
* Override any environment variables that have already been set on your machine with values from your .env file.
*
* example: `require('dotenv').config({ override: true })`
*/
override?: boolean;
}

export interface DotenvApplyOutput {
error?: Error;
}

export interface DotenvApplyInput {
[name: string]: string;
}

/**
* Loads `.env` file contents into process.env.
*
Expand All @@ -71,3 +99,16 @@ export interface DotenvConfigOutput {
*
*/
export function config(options?: DotenvConfigOptions): DotenvConfigOutput;

/**
* Loads `source` json contents into `target` like process.env.
*
* See https://docs.dotenv.org
*
* @param target - the target JSON object
* @param source - the source JSON object
* @param options - additional options. example: `{ debug: true, override: false }`
* @returns {void}
*
*/
export function apply(target: DotenvApplyInput, source: DotenvApplyInput, options?: DotenvConfigOptions): DotenvApplyOutput;
39 changes: 38 additions & 1 deletion lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,49 @@ function decrypt (encrypted, keyStr) {
}
}

function apply (target, source, options = {}) {
const debug = Boolean(options && options.debug)
try {
const override = Boolean(options && options.override)

if (typeof source !== 'object') {
throw Error('Source should be an object')
}

Object.keys(source).forEach(function (key) {
if (!Object.prototype.hasOwnProperty.call(target, key)) {
target[key] = source[key]
} else {
if (override === true) {
target[key] = source[key]
}

if (debug) {
if (override === true) {
_debug(`"${key}" is already defined in source and WAS overwritten`)
} else {
_debug(`"${key}" is already defined in source and was NOT overwritten`)
}
}
}
})
} catch (e) {
if (debug) {
_debug(`Failed to load dotenv ${e.message}`)
}

return { error: e }
}
}

const DotenvModule = {
_configDotenv,
_configVault,
_parseVault,
config,
decrypt,
parse
parse,
apply
}

module.exports._configDotenv = DotenvModule._configDotenv
Expand All @@ -299,5 +335,6 @@ module.exports._parseVault = DotenvModule._parseVault
module.exports.config = DotenvModule.config
module.exports.decrypt = DotenvModule.decrypt
module.exports.parse = DotenvModule.parse
module.exports.apply = DotenvModule.apply

module.exports = DotenvModule
110 changes: 110 additions & 0 deletions tests/test-apply.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const fs = require('fs')

const sinon = require('sinon')
const t = require('tap')

const dotenv = require('../lib/main')

const mockParseResponse = { test: 'foo' }
let readFileSyncStub
let parseStub

t.beforeEach(() => {
readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo')
parseStub = sinon.stub(dotenv, 'parse').returns(mockParseResponse)
})

t.afterEach(() => {
readFileSyncStub.restore()
parseStub.restore()
})

t.test('takes source and check if all keys applied to target', ct => {
ct.plan(1)

const source = { test: 1, home: 2 }
const target = {}

dotenv.apply(target, source)

ct.same(target, source)
})

t.test('does not write over keys already in target', ct => {
ct.plan(1)

const existing = 'bar'
const source = { test: 'test' }
process.env.test = existing

// 'test' returned as value in `beforeEach`. should keep this 'bar'
dotenv.apply(process.env, source)

ct.equal(process.env.test, existing)
})

t.test('does write over keys already in target if override turned on', ct => {
ct.plan(1)

const existing = 'bar'
const source = { test: 'test' }
process.env.test = existing

// 'test' returned as value in `beforeEach`. should change this 'bar' to 'test'
dotenv.apply(process.env, source, { override: true })

ct.equal(process.env.test, source.test)
})

t.test('logs any errors applying when in debug mode but override turned off', ct => {
ct.plan(2)

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

const source = { test: false }
process.env.test = true

dotenv.apply(process.env, source, { debug: true })

ct.not(process.env.test, source.test)
ct.ok(logStub.called)

logStub.restore()
})

t.test('logs applying when debug mode and override turned on', ct => {
ct.plan(1)

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

const source = { test: false }
process.env.test = true

dotenv.apply(process.env, source, { debug: true, override: true })

console.log('process', process.env.test, source.test)

ct.ok(logStub.called)

logStub.restore()
})

t.test('returns any errors thrown on passing not json type', ct => {
ct.plan(1)

const env = dotenv.apply(process.env, '')

ct.type(env.error, Error)
})

t.test('logs any errors thrown on passing not json type and debug is also on', ct => {
ct.plan(1)

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

dotenv.apply(process.env, '', { debug: true })

ct.ok(logStub.called)

logStub.restore()
})