Skip to content
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: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

All notable changes to this project are documented in this file.

## [Unreleased](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.0...main)
## [Unreleased](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.1...main)

## [4.1.1](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.0...v4.1.1) (2026-07-28)

### Changed

- Log `◇ injected env` once per process so Metro does not spam it on every file when `api.cache(false)`.

## [4.1.0](https://github.com/dotenvx/react-native-dotenv/compare/v4.0.0...v4.1.0) (2026-07-28)

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ On transform you'll see a message on stderr (same style as dotenv):
◇ injected env (2) from .env
```

Set `quiet: true` in the plugin options to suppress it.
Metro may prefix worker logs with `|`. Expo also prints its own `env: load .env` line — that is Expo's built-in loader, not this plugin.

Set `quiet: true` in the plugin options to suppress the `◇ injected env` message.

 

Expand Down
17 changes: 13 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ function parseDotenvFile (filepath, verbose = false) {
}
}

// Babel may re-instantiate this plugin per file (e.g. api.cache(false)). Log once
// per process for the same message so Metro workers do not spam stderr.
const loggedInjectMessages = new Set()

function logInjectedEnv (fileEnv, loadedPaths) {
const keysCount = Object.keys(fileEnv).length
if (loadedPaths.length > 0) {
console.error(`◇ injected env (${keysCount}) from ${loadedPaths.join(', ')}`)
} else {
console.error(`◇ injected env (${keysCount})`)
const message = loadedPaths.length > 0
? `◇ injected env (${keysCount}) from ${loadedPaths.join(', ')}`
: `◇ injected env (${keysCount})`

if (loggedInjectMessages.has(message)) {
return
}

loggedInjectMessages.add(message)
console.error(message)
}

function undefObjectAssign (targetObject, sourceObject) {
Expand Down
14 changes: 14 additions & 0 deletions tests/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ describe('react-native-dotenv', () => {
expect(console.error.mock.calls.some(call => /^◇ injected env \(\d+\) from /.test(String(call[0])))).toBe(true)
})

it('should log injected env only once per process', () => {
jest.resetModules()
const plugin = require('../index.js')
const babelOpts = {
configFile: false,
babelrc: false,
plugins: [[plugin, { path: FIXTURES + 'default/.env' }]]
}
transformSync('import { API_KEY } from "@env"', babelOpts)
transformSync('import { API_KEY } from "@env"', babelOpts)
const injectLogs = console.error.mock.calls.filter(call => String(call[0]).includes('injected env'))
expect(injectLogs).toHaveLength(1)
})

it('should not log injected env when quiet', () => {
jest.resetModules()
transformSync('import { API_KEY } from "@env"', {
Expand Down
Loading