From 7bf74ec3c8a421f8348b2737ca850861ce447287 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Mon, 21 Sep 2026 11:17:42 -0700 Subject: [PATCH 1/2] add cli support --- CHANGELOG.md | 8 +++++- README.md | 45 ++++++++++++++++++++++++++++++ package-lock.json | 15 ++++++---- package.json | 4 +-- tests/cli.test.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 tests/cli.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b6d16..5a82c5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.1...main) +## [Unreleased](https://github.com/dotenvx/react-native-dotenv/compare/v5.0.0...main) + +## [5.0.0](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.1...v5.0.0) (2026-09-21) + +### Added + +- Add support for `dotenv run` to share environment variables between build tooling and `@env` imports, including file precedence, safe mode, and Metro restart requirements. ## [4.1.1](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.0...v4.1.1) (2026-07-28) diff --git a/README.md b/README.md index c5bdc5f..f1bdab8 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,51 @@ That's it. Your environment variables from `.env` are available via `@env`! ## Advanced +
with the dotenv CLI
+ +Use dotenv v18's `dotenv run` command when you want the same environment variables available to build tooling and your app's `@env` imports. The Babel plugin still inlines values into the app at build time. + +Install dotenv directly so your package manager makes its CLI available to your project scripts: + +```sh +npm install --save-dev dotenv@^18.0.1 +``` + +Select a file when starting Metro: + +```json +{ + "scripts": { + "start:staging": "dotenv run -f .env.staging -- react-native start --reset-cache" + } +} +``` + +```ini +# .env.staging +API_URL=https://staging.example.org +``` + +Keep the Babel plugin configured as shown in Usage, then import normally: + +```js +import { API_URL } from '@env' + +fetch(`${API_URL}/users`) +``` + +The CLI loads the selected file into the process environment before Metro starts. Existing shell/CI values win unless you pass `--override` to `dotenv run`. The plugin then gives non-empty process environment values priority over its own `.env` files. + +The plugin still loads its usual files; `-f` selects the CLI's file, not the plugin's `path` or `APP_ENV`. This can change precedence: plain `dotenv run` loads `.env` into the process environment, so those values win over the plugin's `.env.local` values. Use the CLI when you intend its injected values to take priority. + +With the default `safe: false`, keys loaded only by the CLI work through `@env` imports. With `safe: true`, those keys must also appear in files the plugin reads. Likewise, `process.env.X` is only inlined for keys in the plugin's files (plus `NODE_ENV`, `BABEL_ENV`, and `envName`). + +Stop Metro and rerun the script after changing CLI-loaded values; its process environment is set at startup. The script resets Metro's cache when restarting. + +The CLI is optional. For values used only by app code, the Babel plugin can continue loading `.env` files on its own. + +
+
with Expo 🧭
```js diff --git a/package-lock.json b/package-lock.json index d0d9c60..0c23745 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "react-native-dotenv", - "version": "4.1.1", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-native-dotenv", - "version": "4.1.1", + "version": "5.0.0", "license": "MIT", "dependencies": { - "dotenv": "^17.4.2" + "dotenv": "^18.0.1" }, "devDependencies": { "@babel/core": "^7.29.7", @@ -2703,10 +2703,13 @@ } }, "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.1.tgz", + "integrity": "sha512-0eR4m4D/jH5eaI3evo2ZqqMii5mrTCR12v12VChdie6O1gPHs7XAjzTep0CWc1Bd+oNx3vVfbi734dK4zghigw==", "license": "BSD-2-Clause", + "bin": { + "dotenv": "dist/index.cjs" + }, "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index 3d6ec9a..38f68e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-dotenv", - "version": "4.1.1", + "version": "5.0.0", "description": "Load .env into React Native with import statements. A Babel plugin that inlines environment variables at build time.", "repository": { "type": "git", @@ -26,7 +26,7 @@ "12factor" ], "dependencies": { - "dotenv": "^17.4.2" + "dotenv": "^18.0.1" }, "devDependencies": { "@babel/core": "^7.29.7", diff --git a/tests/cli.test.js b/tests/cli.test.js new file mode 100644 index 0000000..bde4d68 --- /dev/null +++ b/tests/cli.test.js @@ -0,0 +1,70 @@ +const { execFileSync } = require('child_process') +const fs = require('fs') +const os = require('os') +const path = require('path') + +describe('dotenv run integration', () => { + let directory + let env + const dotenvPackagePath = require.resolve('dotenv/package.json') + const cli = path.resolve(path.dirname(dotenvPackagePath), require(dotenvPackagePath).bin.dotenv) + + beforeEach(() => { + directory = fs.mkdtempSync(path.join(os.tmpdir(), 'react-native-dotenv-cli-')) + env = { ...process.env } + for (const key of Object.keys(env)) { + if (/^(DOTENV_|RN_DOTENV_CLI_)/.test(key) || ['NODE_ENV', 'BABEL_ENV', 'APP_ENV'].includes(key)) { + delete env[key] + } + } + fs.writeFileSync(path.join(directory, '.env'), 'RN_DOTENV_CLI_URL=base\n') + fs.writeFileSync(path.join(directory, '.env.local'), 'RN_DOTENV_CLI_URL=local\n') + fs.writeFileSync(path.join(directory, '.env.staging'), 'RN_DOTENV_CLI_URL=staging\nRN_DOTENV_CLI_ONLY=extra\n') + }) + + afterEach(() => { + fs.rmSync(directory, { recursive: true, force: true }) + }) + + function transform (args, source, options = {}) { + const script = ` + const { transformSync } = require(${JSON.stringify(require.resolve('@babel/core'))}) + const result = transformSync(${JSON.stringify(source)}, { + configFile: false, + babelrc: false, + plugins: [[${JSON.stringify(require.resolve('../index.js'))}, ${JSON.stringify({ quiet: true, ...options })}]] + }) + console.log(result.code) + ` + return execFileSync(process.execPath, [cli, 'run', '-q', ...args, '--', process.execPath, '-e', script], { + cwd: directory, + env, + encoding: 'utf8' + }).trim() + } + + it('inlines CLI values through imports while leaving CLI-only process.env references intact', () => { + expect(transform(['-f', '.env.staging'], + 'import { RN_DOTENV_CLI_URL, RN_DOTENV_CLI_ONLY } from "@env"; console.log(RN_DOTENV_CLI_URL, RN_DOTENV_CLI_ONLY, process.env.RN_DOTENV_CLI_URL, process.env.RN_DOTENV_CLI_ONLY)' + )).toBe('console.log("staging", "extra", "staging", process.env.RN_DOTENV_CLI_ONLY);') + }) + + it('preserves safe mode restrictions on CLI-only imports', () => { + expect(transform(['-f', '.env.staging'], + 'import { RN_DOTENV_CLI_URL, RN_DOTENV_CLI_ONLY } from "@env"; console.log(RN_DOTENV_CLI_URL, RN_DOTENV_CLI_ONLY)', + { safe: true } + )).toBe('console.log("staging", undefined);') + }) + + it('gives shell values priority unless the CLI uses --override', () => { + env.RN_DOTENV_CLI_URL = 'shell' + const source = 'import { RN_DOTENV_CLI_URL } from "@env"; console.log(RN_DOTENV_CLI_URL)' + expect(transform(['-f', '.env.staging'], source)).toBe('console.log("shell");') + expect(transform(['--override', '-f', '.env.staging'], source)).toBe('console.log("staging");') + }) + + it('gives CLI-loaded .env values priority over plugin-loaded .env.local values', () => { + expect(transform([], 'import { RN_DOTENV_CLI_URL } from "@env"; console.log(RN_DOTENV_CLI_URL)')) + .toBe('console.log("base");') + }) +}) From 8f1cc49d200ffb20e3fb5f780f5ce82ec4d8adb8 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Mon, 21 Sep 2026 11:19:57 -0700 Subject: [PATCH 2/2] npm audit --- CHANGELOG.md | 4 ++++ package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a82c5e..e2e0eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project are documented in this file. ## [Unreleased](https://github.com/dotenvx/react-native-dotenv/compare/v5.0.0...main) +### Fixed + +- Update vulnerable `brace-expansion` and `js-yaml` dependencies to patched versions and raise the `brace-expansion` override minimum to 5.0.9. + ## [5.0.0](https://github.com/dotenvx/react-native-dotenv/compare/v4.1.1...v5.0.0) (2026-09-21) ### Added diff --git a/package-lock.json b/package-lock.json index 0c23745..74eacfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -611,9 +611,9 @@ "license": "Python-2.0" }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -2174,9 +2174,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3490,9 +3490,9 @@ } }, "node_modules/eslint/node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -5507,9 +5507,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", - "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 38f68e4..0ee5e39 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "standard": "^17.1.2" }, "overrides": { - "brace-expansion": "^5.0.8", + "brace-expansion": "^5.0.9", "minimatch": "^10.2.5" }, "author": "@motdotla",