From fc3a74e31afad4cfc2ba464998ecb0ebee4007ba Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Tue, 28 Jul 2026 13:47:27 -0700 Subject: [PATCH 1/3] add expo example --- README.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe72e31..afdc630 100644 --- a/README.md +++ b/README.md @@ -233,9 +233,37 @@ For host/CI-only values, use `@env` imports — or put the key in `.env` and let
Expo
-Expo now has [built-in environment variable support](https://docs.expo.dev/guides/environment-variables/). Evaluate if you still need this plugin. +Expo has [built-in environment variable support](https://docs.expo.dev/guides/environment-variables/). Use this plugin when you want `@env` imports or multi-env files (e.g. `.env.staging` via `APP_ENV`). + +```js +// babel.config.js +module.exports = function (api) { + api.cache(false) + return { + presets: ['babel-preset-expo'], + plugins: [ + ['module:react-native-dotenv'] + ] + } +} +``` + +```ini +# .env +HELLO="Universe" +``` + +```js +// app/index.tsx +import { HELLO } from '@env' +import { Text } from 'react-native' + +export default function HomeScreen() { + return Hello {HELLO} +} +``` -Preview [the expo test app](https://github.com/goatandsheep/react-native-dotenv-expo-test). +Then start with a clean Metro cache: `npx expo start --clear`.
Multi-env
From aff12aec0d3c728c2a30fd26fbddeb7a07721758 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Tue, 28 Jul 2026 13:49:15 -0700 Subject: [PATCH 2/3] add injecting message --- CHANGELOG.md | 8 +++++-- README.md | 23 +++++++++++++++++- index.js | 57 +++++++++++++++++++++++++++++++++------------ tests/index.test.js | 33 +++++++++++++++++++++----- 4 files changed, 97 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a09fb57..aff935d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,13 @@ All notable changes to this project are documented in this file. -[Unreleased](https://github.com/dotenvx/dotenvx/compare/v4.0.0...main) +## [Unreleased](https://github.com/dotenvx/react-native-dotenv/compare/v4.0.0...main) -## [4.0.0](https://github.com/dotenvx/dotenvx/compare/v3.4.12...v4.0.0) (2026-07-28) +### Added + +- Log `◇ injected env (N) from .env` to stderr on transform (same style as dotenv). Set `quiet: true` to suppress. + +## [4.0.0](https://github.com/dotenvx/react-native-dotenv/compare/v3.4.12...v4.0.0) (2026-07-28) ### Fixed diff --git a/README.md b/README.md index afdc630..1b02926 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ fetch(`${API_URL}/users`) That's it. Your environment variables from `.env` are available via `@env`. +On transform you'll see a message on stderr (same style as dotenv): + +```text +◇ injected env (2) from .env +``` +   ## Advanced @@ -204,7 +210,7 @@ When set to `false`, an error will be thrown.
verbose (default: false)
-Print the active dotenv mode while transforming. +Also print the active dotenv mode to stderr while transforming. ```json { @@ -216,6 +222,21 @@ Print the active dotenv mode while transforming. } ``` +
+
quiet (default: false)
+ +Suppress the stderr inject message (`◇ injected env (N) from .env`). + +```json +{ + "plugins": [ + ["module:react-native-dotenv", { + "quiet": true + }] + ] +} +``` +
process.env
diff --git a/index.js b/index.js index 59aa60c..953c0c4 100644 --- a/index.js +++ b/index.js @@ -3,20 +3,26 @@ const path = require('path') const dotenv = require('dotenv') function parseDotenvFile (filepath, verbose = false) { - let content - try { - content = fs.readFileSync(filepath) + const content = fs.readFileSync(filepath) + return { parsed: dotenv.parse(content), exists: true } } catch (error) { // The env file does not exist. if (verbose) { console.error('react-native-dotenv', error) } - return {} + return { parsed: {}, exists: false } } +} - return dotenv.parse(content) // +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})`) + } } function undefObjectAssign (targetObject, sourceObject) { @@ -67,6 +73,7 @@ module.exports = (api, options) => { safe: false, allowUndefined: true, verbose: false, + quiet: false, ...options } const babelMode = process.env[options.envName] || (process.env.BABEL_ENV && process.env.BABEL_ENV !== 'undefined' && process.env.BABEL_ENV !== 'development' && process.env.BABEL_ENV) || process.env.NODE_ENV || 'development' @@ -75,7 +82,7 @@ module.exports = (api, options) => { const modeLocalFilePath = options.path + '.' + babelMode + '.local' if (options.verbose) { - console.log('dotenvMode', babelMode) + console.error(`◇ dotenvMode ${babelMode}`) if (process.env[options.envName] === 'production' || process.env[options.envName] === 'development') { console.error('APP_ENV error', 'cannot use APP_ENV=development or APP_ENV=production') } @@ -87,12 +94,18 @@ module.exports = (api, options) => { api.cache.using(() => mtime(modeLocalFilePath)) const dotenvTemporary = undefObjectAssign({}, process.env) - const parsed = parseDotenvFile(options.path, options.verbose) - const localParsed = parseDotenvFile(localFilePath, options.verbose) - const modeParsed = parseDotenvFile(modeFilePath, options.verbose) - const modeLocalParsed = parseDotenvFile(modeLocalFilePath, options.verbose) + const parsedFile = parseDotenvFile(options.path, options.verbose) + const localFile = parseDotenvFile(localFilePath, options.verbose) + const modeFile = parseDotenvFile(modeFilePath, options.verbose) + const modeLocalFile = parseDotenvFile(modeLocalFilePath, options.verbose) const modeExceptions = ['NODE_ENV', 'BABEL_ENV', options.envName] - const fileEnv = undefObjectAssign(undefObjectAssign(undefObjectAssign(parsed, modeParsed), localParsed), modeLocalParsed) + const fileEnv = undefObjectAssign( + undefObjectAssign( + undefObjectAssign(parsedFile.parsed, modeFile.parsed), + localFile.parsed + ), + modeLocalFile.parsed + ) // process.env.X is only inlined for keys from .env files (+ mode exceptions). // That stops build-tooling pollution (e.g. Metro jest-worker) without hardcoding @@ -106,10 +119,24 @@ module.exports = (api, options) => { ? safeObjectAssign(undefObjectAssign({}, fileEnv), dotenvTemporary, modeExceptions) : undefObjectAssign(undefObjectAssign({}, fileEnv), dotenvTemporary) - api.addExternalDependency(path.resolve(options.path)) - api.addExternalDependency(path.resolve(modeFilePath)) - api.addExternalDependency(path.resolve(localFilePath)) - api.addExternalDependency(path.resolve(modeLocalFilePath)) + if (options.verbose || !options.quiet) { + const loadedPaths = [ + [options.path, parsedFile.exists], + [modeFilePath, modeFile.exists], + [localFilePath, localFile.exists], + [modeLocalFilePath, modeLocalFile.exists] + ] + .filter(([, exists]) => exists) + .map(([filepath]) => filepath) + logInjectedEnv(fileEnv, loadedPaths) + } + + if (typeof api.addExternalDependency === 'function') { + api.addExternalDependency(path.resolve(options.path)) + api.addExternalDependency(path.resolve(modeFilePath)) + api.addExternalDependency(path.resolve(localFilePath)) + api.addExternalDependency(path.resolve(modeLocalFilePath)) + } return ({ name: 'dotenv-import', diff --git a/tests/index.test.js b/tests/index.test.js index 1aba91c..55f75c6 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -8,7 +8,11 @@ describe('react-native-dotenv', () => { } const OLD_ENV = process.env + beforeEach(() => { + jest.spyOn(console, 'error').mockImplementation(() => {}) + }) afterEach(() => { + console.error.mockRestore() jest.resetModules() process.env = { ...OLD_ENV } }) @@ -31,10 +35,29 @@ describe('react-native-dotenv', () => { }) it('should print the environment if setting to verbose', () => { - console.log = jest.fn() const { code } = transformFileSync(FIXTURES + 'verbose/source.js') expect(code).toBe('console.log("abc123");\nconsole.log("username");') - expect(console.log.mock.calls[0][1]).toBe('test') + expect(console.error.mock.calls.some(call => String(call[0]).includes('dotenvMode test'))).toBe(true) + }) + + it('should log injected env to stderr', () => { + jest.resetModules() + transformSync('import { API_KEY } from "@env"; console.log(API_KEY)', { + configFile: false, + babelrc: false, + plugins: [[require('../index.js'), { path: FIXTURES + 'default/.env' }]] + }) + expect(console.error.mock.calls.some(call => /^◇ injected env \(\d+\) from /.test(String(call[0])))).toBe(true) + }) + + it('should not log injected env when quiet', () => { + jest.resetModules() + transformSync('import { API_KEY } from "@env"', { + configFile: false, + babelrc: false, + plugins: [[require('../index.js'), { path: FIXTURES + 'default/.env', quiet: true }]] + }) + expect(console.error.mock.calls.some(call => String(call[0]).includes('injected env'))).toBe(false) }) it('should allow importing variables already defined in the environment', () => { @@ -151,21 +174,19 @@ describe('react-native-dotenv', () => { }) it('should fail to load APP_ENV development', () => { - console.error = jest.fn() process.env.APP_ENV = 'development' const { code } = transformFileSync(FIXTURES + 'app-env-development/source.js') expect(code).toBe('console.log("never");\nconsole.log("this-should-not-appear");') - expect(console.error.mock.calls[0][0]).toBe('APP_ENV error') + expect(console.error.mock.calls.some(call => call[0] === 'APP_ENV error')).toBe(true) }) it('should fail to load APP_ENV production', () => { - console.error = jest.fn() process.env.APP_ENV = 'production' const { code } = transformFileSync(FIXTURES + 'app-env-production/source.js') expect(code).toBe('console.log("never");\nconsole.log("this-should-not-appear");') - expect(console.error.mock.calls[0][0]).toBe('APP_ENV error') + expect(console.error.mock.calls.some(call => call[0] === 'APP_ENV error')).toBe(true) }) it('should load MY_ENV specific env file', () => { From 0b6378f93baec700a0d28281f9bd6071532fb80b Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Tue, 28 Jul 2026 13:49:27 -0700 Subject: [PATCH 3/3] update README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1b02926..1e79b47 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ 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. +   ## Advanced