diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9d3976..9b2f0006 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,33 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. -## [Unreleased](https://github.com/motdotla/dotenv/compare/v17.1.0...master) +## [Unreleased](https://github.com/motdotla/dotenv/compare/v17.2.0...master) + +## [17.2.0](https://github.com/motdotla/dotenv/compare/v17.1.0...v17.2.0) (2025-07-09) + +### Added + +* Optionally specify `DOTENV_CONFIG_QUIET=true` in your environment or `.env` file to quiet the runtime log ([#889](https://github.com/motdotla/dotenv/pull/889)) +* Just like dotenv any `DOTENV_CONFIG_` environment variables take precedence over any code set options like `({quiet: false})` + +```ini +# .env +DOTENV_CONFIG_QUIET=true +HELLO="World" +``` +```js +// index.js +require('dotenv').config() +console.log(`Hello ${process.env.HELLO}`) +``` +```sh +$ node index.js +Hello World + +or + +$ DOTENV_CONFIG_QUIET=true node index.js +``` ## [17.1.0](https://github.com/motdotla/dotenv/compare/v17.0.1...v17.1.0) (2025-07-07) diff --git a/lib/main.js b/lib/main.js index e91234e3..2ac4c9c7 100644 --- a/lib/main.js +++ b/lib/main.js @@ -25,6 +25,13 @@ function _getRandomTip () { return TIPS[Math.floor(Math.random() * TIPS.length)] } +function parseBoolean (value) { + if (typeof value === 'string') { + return !['false', '0', 'no', 'off', ''].includes(value.toLowerCase()) + } + return Boolean(value) +} + function supportsAnsi () { return process.stdout.isTTY // && process.env.TERM !== 'dumb' } @@ -216,8 +223,8 @@ function _resolveHome (envPath) { } function _configVault (options) { - const debug = Boolean(options && options.debug) - const quiet = Boolean(options && options.quiet) + const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || (options && options.debug)) + const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || (options && options.quiet)) if (debug || !quiet) { _log('Loading env from encrypted .env.vault') @@ -238,8 +245,12 @@ function _configVault (options) { function configDotenv (options) { const dotenvPath = path.resolve(process.cwd(), '.env') let encoding = 'utf8' - const debug = Boolean(options && options.debug) - const quiet = Boolean(options && options.quiet) + let processEnv = process.env + if (options && options.processEnv != null) { + processEnv = options.processEnv + } + let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || (options && options.debug)) + let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || (options && options.quiet)) if (options && options.encoding) { encoding = options.encoding @@ -279,13 +290,12 @@ function configDotenv (options) { } } - let processEnv = process.env - if (options && options.processEnv != null) { - processEnv = options.processEnv - } - const populated = DotenvModule.populate(processEnv, parsedAll, options) + // handle user settings DOTENV_CONFIG_ options inside .env file(s) + debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug) + quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet) + if (debug || !quiet) { const keysCount = Object.keys(populated).length const shortPaths = [] diff --git a/tests/test-config-vault.js b/tests/test-config-vault.js index 5cc57580..a43f8e59 100644 --- a/tests/test-config-vault.js +++ b/tests/test-config-vault.js @@ -25,60 +25,6 @@ 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('does log by default', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: testPath }) - ct.ok(logStub.called) -}) - -t.test('does not log if quiet flag passed true', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: testPath, quiet: true }) - ct.ok(logStub.notCalled) -}) - -t.test('does log if quiet flag false', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: testPath, quiet: false }) - ct.ok(logStub.called) -}) - -t.test('does log if quiet flag present and undefined/null', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: testPath, quiet: undefined }) - ct.ok(logStub.called) -}) - -t.test('logs if debug set', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: testPath, debug: true }) - ct.ok(logStub.called) -}) - t.test('does log when testPath calls to .env.vault directly (interpret what the user meant)', ct => { ct.plan(1) @@ -88,18 +34,10 @@ t.test('does log when testPath calls to .env.vault directly (interpret what the ct.ok(logStub.called) }) -t.test('logs when testPath calls to .env.vault directly (interpret what the user meant) and debug true', ct => { - ct.plan(1) - - logStub = sinon.stub(console, 'log') - - dotenv.config({ path: `${testPath}.vault`, debug: true }) - ct.ok(logStub.called) -}) - t.test('warns if DOTENV_KEY exists but .env.vault does not exist', ct => { ct.plan(1) + const testPath = 'tests/.env' logStub = sinon.stub(console, 'log') const existsSync = sinon.stub(fs, 'existsSync').returns(false) // make .env.vault not exist @@ -113,6 +51,7 @@ t.test('warns if DOTENV_KEY exists but .env.vault does not exist', ct => { t.test('warns if DOTENV_KEY exists but .env.vault does not exist (set as array)', ct => { ct.plan(1) + const testPath = 'tests/.env' logStub = sinon.stub(console, 'log') const existsSync = sinon.stub(fs, 'existsSync').returns(false) // make .env.vault not exist @@ -123,6 +62,15 @@ t.test('warns if DOTENV_KEY exists but .env.vault does not exist (set as array)' ct.end() }) +t.test('logs when testPath calls to .env.vault directly (interpret what the user meant) and debug true', ct => { + ct.plan(1) + + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: `${testPath}.vault`, debug: true }) + ct.ok(logStub.called) +}) + t.test('returns parsed object', ct => { ct.plan(1) diff --git a/tests/test-config.js b/tests/test-config.js index a990d231..58b158a0 100644 --- a/tests/test-config.js +++ b/tests/test-config.js @@ -1,16 +1,22 @@ const fs = require('fs') const os = require('os') const path = require('path') - const sinon = require('sinon') const t = require('tap') const dotenv = require('../lib/main') +let logStub + t.beforeEach(() => { + logStub = null delete process.env.BASIC // reset }) +t.afterEach(() => { + if (logStub) logStub.restore() +}) + t.test('takes string for path option', ct => { const testPath = 'tests/.env' const env = dotenv.config({ path: testPath }) @@ -114,12 +120,11 @@ t.test('takes option for encoding', ct => { }) t.test('takes option for debug', ct => { - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') dotenv.config({ debug: 'true' }) ct.ok(logStub.called) - logStub.restore() ct.end() }) @@ -226,7 +231,7 @@ t.test('returns any errors thrown from reading file or parsing', ct => { t.test('logs any errors thrown from reading file or parsing when in debug mode', ct => { ct.plan(2) - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') readFileSyncStub.throws() @@ -235,24 +240,21 @@ t.test('logs any errors thrown from reading file or parsing when in debug mode', ct.ok(logStub.called) ct.type(env.error, Error) - logStub.restore() readFileSyncStub.restore() }) t.test('logs any errors parsing when in debug and override mode', ct => { ct.plan(1) - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') dotenv.config({ debug: true, override: true }) ct.ok(logStub.called) - - logStub.restore() }) t.test('deals with file:// path', ct => { - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const testPath = 'file:///tests/.env' const env = dotenv.config({ path: testPath }) @@ -263,13 +265,11 @@ t.test('deals with file:// path', ct => { ct.ok(logStub.called) - logStub.restore() - ct.end() }) t.test('deals with file:// path and debug true', ct => { - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const testPath = 'file:///tests/.env' const env = dotenv.config({ path: testPath, debug: true }) @@ -280,13 +280,11 @@ t.test('deals with file:// path and debug true', ct => { ct.ok(logStub.called) - logStub.restore() - ct.end() }) t.test('path.relative fails somehow', ct => { - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const pathRelativeStub = sinon.stub(path, 'relative').throws(new Error('fail')) const testPath = 'file:///tests/.env' @@ -298,7 +296,6 @@ t.test('path.relative fails somehow', ct => { ct.ok(logStub.called) - logStub.restore() pathRelativeStub.restore() ct.end() @@ -310,7 +307,7 @@ t.test('displays random tips from the tips array', ct => { const originalTTY = process.stdout.isTTY process.stdout.isTTY = true - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const testPath = 'tests/.env' // Test that tips are displayed (run config multiple times to see variation) @@ -359,8 +356,6 @@ t.test('displays random tips from the tips array', ct => { // Restore process.stdout.isTTY = originalTTY - - logStub.restore() ct.end() }) @@ -370,7 +365,7 @@ t.test('displays random tips from the tips array with fallback for isTTY false', const originalTTY = process.stdout.isTTY process.stdout.isTTY = undefined - const logStub = sinon.stub(console, 'log') + logStub = sinon.stub(console, 'log') const testPath = 'tests/.env' // Test that tips are displayed (run config multiple times to see variation) @@ -419,7 +414,88 @@ t.test('displays random tips from the tips array with fallback for isTTY false', // Restore process.stdout.isTTY = originalTTY - - logStub.restore() ct.end() }) + +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('does log by default', ct => { + ct.plan(1) + + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath }) + ct.ok(logStub.called) +}) + +t.test('does not log if quiet flag passed true', ct => { + ct.plan(1) + + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath, quiet: true }) + ct.ok(logStub.notCalled) +}) + +t.test('does not log if process.env.DOTENV_CONFIG_QUIET is true', ct => { + ct.plan(1) + + process.env.DOTENV_CONFIG_QUIET = 'true' + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath }) + ct.ok(logStub.notCalled) + delete process.env.DOTENV_CONFIG_QUIET +}) + +t.test('does log if quiet flag false', ct => { + ct.plan(1) + + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath, quiet: false }) + ct.ok(logStub.called) +}) + +t.test('does log if process.env.DOTENV_CONFIG_QUIET is false', ct => { + ct.plan(1) + + process.env.DOTENV_CONFIG_QUIET = 'false' + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath }) + ct.ok(logStub.called) + delete process.env.DOTENV_CONFIG_QUIET +}) + +t.test('does log if quiet flag present and undefined/null', ct => { + ct.plan(1) + + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath, quiet: undefined }) + ct.ok(logStub.called) +}) + +t.test('logs if debug set', ct => { + ct.plan(1) + + const testPath = 'tests/.env' + logStub = sinon.stub(console, 'log') + + dotenv.config({ path: testPath, debug: true }) + ct.ok(logStub.called) +})