Comparing version 16.0.3 to 16.3.1
@@ -5,4 +5,67 @@ # Changelog | ||
## [Unreleased](https://github.com/motdotla/dotenv/compare/v16.0.3...master) | ||
## [Unreleased](https://github.com/motdotla/dotenv/compare/v16.3.1...master) | ||
## [16.3.1](https://github.com/motdotla/dotenv/compare/v16.3.0...v16.3.1) (2023-06-17) | ||
### Added | ||
- Add missing type definitions for `processEnv` and `DOTENV_KEY` options. [#756](https://github.com/motdotla/dotenv/pull/756) | ||
## [16.3.0](https://github.com/motdotla/dotenv/compare/v16.2.0...v16.3.0) (2023-06-16) | ||
### Added | ||
- Optionally pass `DOTENV_KEY` to options rather than relying on `process.env.DOTENV_KEY`. Defaults to `process.env.DOTENV_KEY` [#754](https://github.com/motdotla/dotenv/pull/754) | ||
## [16.2.0](https://github.com/motdotla/dotenv/compare/v16.1.4...v16.2.0) (2023-06-15) | ||
### Added | ||
- Optionally write to your own target object rather than `process.env`. Defaults to `process.env`. [#753](https://github.com/motdotla/dotenv/pull/753) | ||
- Add import type URL to types file [#751](https://github.com/motdotla/dotenv/pull/751) | ||
## [16.1.4](https://github.com/motdotla/dotenv/compare/v16.1.3...v16.1.4) (2023-06-04) | ||
### Added | ||
- Added `.github/` to `.npmignore` [#747](https://github.com/motdotla/dotenv/pull/747) | ||
## [16.1.3](https://github.com/motdotla/dotenv/compare/v16.1.2...v16.1.3) (2023-05-31) | ||
### Removed | ||
- Removed `browser` keys for `path`, `os`, and `crypto` in package.json. These were set to false incorrectly as of 16.1. Instead, if using dotenv on the front-end make sure to include polyfills for `path`, `os`, and `crypto`. [node-polyfill-webpack-plugin](https://github.com/Richienb/node-polyfill-webpack-plugin) provides these. | ||
## [16.1.2](https://github.com/motdotla/dotenv/compare/v16.1.1...v16.1.2) (2023-05-31) | ||
### Changed | ||
- Exposed private function `_configDotenv` as `configDotenv`. [#744](https://github.com/motdotla/dotenv/pull/744) | ||
## [16.1.1](https://github.com/motdotla/dotenv/compare/v16.1.0...v16.1.1) (2023-05-30) | ||
### Added | ||
- Added type definition for `decrypt` function | ||
### Changed | ||
- Fixed `{crypto: false}` in `packageJson.browser` | ||
## [16.1.0](https://github.com/motdotla/dotenv/compare/v16.0.3...v16.1.0) (2023-05-30) | ||
### Added | ||
- Add `populate` convenience method [#733](https://github.com/motdotla/dotenv/pull/733) | ||
- Accept URL as path option [#720](https://github.com/motdotla/dotenv/pull/720) | ||
- Add dotenv to `npm fund` command | ||
- Spanish language README [#698](https://github.com/motdotla/dotenv/pull/698) | ||
- Add `.env.vault` support. π ([#730](https://github.com/motdotla/dotenv/pull/730)) | ||
βΉοΈ `.env.vault` extends the `.env` file format standard with a localized encrypted vault file. Package it securely with your production code deploys. It's cloud agnostic so that you can deploy your secrets anywhere βΒ without [risky third-party integrations](https://techcrunch.com/2023/01/05/circleci-breach/). [read more](https://github.com/motdotla/dotenv#-deploying) | ||
### Changed | ||
- Fixed "cannot resolve 'fs'" error on tools like Replit [#693](https://github.com/motdotla/dotenv/pull/693) | ||
## [16.0.3](https://github.com/motdotla/dotenv/compare/v16.0.2...v16.0.3) (2022-09-29) | ||
@@ -9,0 +72,0 @@ |
@@ -1,2 +0,2 @@ | ||
const re = /^dotenv_config_(encoding|path|debug|override)=(.+)$/ | ||
const re = /^dotenv_config_(encoding|path|debug|override|DOTENV_KEY)=(.+)$/ | ||
@@ -3,0 +3,0 @@ module.exports = function optionMatcher (args) { |
@@ -20,2 +20,6 @@ // ../config.js accepts options via environment variables | ||
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) { | ||
options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY | ||
} | ||
module.exports = options |
// TypeScript Version: 3.0 | ||
/// <reference types="node" /> | ||
import type { URL } from 'node:url'; | ||
@@ -29,3 +30,3 @@ export interface DotenvParseOutput { | ||
*/ | ||
path?: string; | ||
path?: string | URL; | ||
@@ -58,2 +59,20 @@ /** | ||
override?: boolean; | ||
/** | ||
* Default: `process.env` | ||
* | ||
* Specify an object to write your secrets to. Defaults to process.env environment variables. | ||
* | ||
* example: `const processEnv = {}; require('dotenv').config({ processEnv: processEnv })` | ||
*/ | ||
processEnv?: DotenvPopulateInput; | ||
/** | ||
* Default: `undefined` | ||
* | ||
* Pass the DOTENV_KEY directly to config options. Defaults to looking for process.env.DOTENV_KEY environment variable. Note this only applies to decrypting .env.vault files. If passed as null or undefined, or not passed at all, dotenv falls back to its traditional job of parsing a .env file. | ||
* | ||
* example: `require('dotenv').config({ DOTENV_KEY: 'dotenv://:key_1234β¦@dotenv.org/vault/.env.vault?environment=production' })` | ||
*/ | ||
DOTENV_KEY?: string; | ||
} | ||
@@ -66,3 +85,42 @@ | ||
export interface DotenvPopulateOptions { | ||
/** | ||
* 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 DotenvPopulateOutput { | ||
error?: Error; | ||
} | ||
export interface DotenvPopulateInput { | ||
[name: string]: string; | ||
} | ||
/** | ||
* Loads `.env` file contents into process.env by default. If `DOTENV_KEY` is present, it smartly attempts to load encrypted `.env.vault` file contents into process.env. | ||
* | ||
* See https://docs.dotenv.org | ||
* | ||
* @param options - additional options. example: `{ path: './custom/path', encoding: 'latin1', debug: true, override: false }` | ||
* @returns an object with a `parsed` key if successful or `error` key if an error occurred. example: { parsed: { KEY: 'value' } } | ||
* | ||
*/ | ||
export function config(options?: DotenvConfigOptions): DotenvConfigOutput; | ||
/** | ||
* Loads `.env` file contents into process.env. | ||
@@ -76,2 +134,27 @@ * | ||
*/ | ||
export function config(options?: DotenvConfigOptions): DotenvConfigOutput; | ||
export function configDotenv(options?: DotenvConfigOptions): DotenvConfigOutput; | ||
/** | ||
* Loads `source` json contents into `target` like process.env. | ||
* | ||
* See https://docs.dotenv.org | ||
* | ||
* @param processEnv - the target JSON object. in most cases use process.env but you can also pass your own JSON object | ||
* @param parsed - the source JSON object | ||
* @param options - additional options. example: `{ debug: true, override: false }` | ||
* @returns {void} | ||
* | ||
*/ | ||
export function populate(processEnv: DotenvPopulateInput, parsed: DotenvPopulateInput, options?: DotenvConfigOptions): DotenvPopulateOutput; | ||
/** | ||
* Decrypt ciphertext | ||
* | ||
* See https://docs.dotenv.org | ||
* | ||
* @param encrypted - the encrypted ciphertext string | ||
* @param keyStr - the decryption key string | ||
* @returns {string} | ||
* | ||
*/ | ||
export function decrypt(encrypted: string, keyStr: string): string; |
246
lib/main.js
const fs = require('fs') | ||
const path = require('path') | ||
const os = require('os') | ||
const crypto = require('crypto') | ||
const packageJson = require('../package.json') | ||
@@ -10,3 +11,3 @@ | ||
// Parser src into an Object | ||
// Parse src into an Object | ||
function parse (src) { | ||
@@ -50,6 +51,115 @@ const obj = {} | ||
function _parseVault (options) { | ||
const vaultPath = _vaultPath(options) | ||
// Parse .env.vault | ||
const result = DotenvModule.configDotenv({ path: vaultPath }) | ||
if (!result.parsed) { | ||
throw new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`) | ||
} | ||
// handle scenario for comma separated keys - for use with key rotation | ||
// example: DOTENV_KEY="dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenv.org/vault/.env.vault?environment=prod" | ||
const keys = _dotenvKey(options).split(',') | ||
const length = keys.length | ||
let decrypted | ||
for (let i = 0; i < length; i++) { | ||
try { | ||
// Get full key | ||
const key = keys[i].trim() | ||
// Get instructions for decrypt | ||
const attrs = _instructions(result, key) | ||
// Decrypt | ||
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key) | ||
break | ||
} catch (error) { | ||
// last key | ||
if (i + 1 >= length) { | ||
throw error | ||
} | ||
// try next key | ||
} | ||
} | ||
// Parse decrypted .env string | ||
return DotenvModule.parse(decrypted) | ||
} | ||
function _log (message) { | ||
console.log(`[dotenv@${version}][INFO] ${message}`) | ||
} | ||
function _warn (message) { | ||
console.log(`[dotenv@${version}][WARN] ${message}`) | ||
} | ||
function _debug (message) { | ||
console.log(`[dotenv@${version}][DEBUG] ${message}`) | ||
} | ||
function _dotenvKey (options) { | ||
// prioritize developer directly setting options.DOTENV_KEY | ||
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) { | ||
return options.DOTENV_KEY | ||
} | ||
// secondary infra already contains a DOTENV_KEY environment variable | ||
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) { | ||
return process.env.DOTENV_KEY | ||
} | ||
// fallback to empty string | ||
return '' | ||
} | ||
function _instructions (result, dotenvKey) { | ||
// Parse DOTENV_KEY. Format is a URI | ||
let uri | ||
try { | ||
uri = new URL(dotenvKey) | ||
} catch (error) { | ||
if (error.code === 'ERR_INVALID_URL') { | ||
throw new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development') | ||
} | ||
throw error | ||
} | ||
// Get decrypt key | ||
const key = uri.password | ||
if (!key) { | ||
throw new Error('INVALID_DOTENV_KEY: Missing key part') | ||
} | ||
// Get environment | ||
const environment = uri.searchParams.get('environment') | ||
if (!environment) { | ||
throw new Error('INVALID_DOTENV_KEY: Missing environment part') | ||
} | ||
// Get ciphertext payload | ||
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}` | ||
const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION | ||
if (!ciphertext) { | ||
throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`) | ||
} | ||
return { ciphertext, key } | ||
} | ||
function _vaultPath (options) { | ||
let dotenvPath = path.resolve(process.cwd(), '.env') | ||
if (options && options.path && options.path.length > 0) { | ||
dotenvPath = options.path | ||
} | ||
// Locate .env.vault | ||
return dotenvPath.endsWith('.vault') ? dotenvPath : `${dotenvPath}.vault` | ||
} | ||
function _resolveHome (envPath) { | ||
@@ -59,8 +169,21 @@ return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath | ||
// Populates process.env from .env file | ||
function config (options) { | ||
function _configVault (options) { | ||
_log('Loading env from encrypted .env.vault') | ||
const parsed = DotenvModule._parseVault(options) | ||
let processEnv = process.env | ||
if (options && options.processEnv != null) { | ||
processEnv = options.processEnv | ||
} | ||
DotenvModule.populate(processEnv, parsed, options) | ||
return { parsed } | ||
} | ||
function configDotenv (options) { | ||
let dotenvPath = path.resolve(process.cwd(), '.env') | ||
let encoding = 'utf8' | ||
const debug = Boolean(options && options.debug) | ||
const override = Boolean(options && options.override) | ||
@@ -80,19 +203,8 @@ if (options) { | ||
Object.keys(parsed).forEach(function (key) { | ||
if (!Object.prototype.hasOwnProperty.call(process.env, key)) { | ||
process.env[key] = parsed[key] | ||
} else { | ||
if (override === true) { | ||
process.env[key] = parsed[key] | ||
} | ||
let processEnv = process.env | ||
if (options && options.processEnv != null) { | ||
processEnv = options.processEnv | ||
} | ||
if (debug) { | ||
if (override === true) { | ||
_log(`"${key}" is already defined in \`process.env\` and WAS overwritten`) | ||
} else { | ||
_log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`) | ||
} | ||
} | ||
} | ||
}) | ||
DotenvModule.populate(processEnv, parsed, options) | ||
@@ -102,3 +214,3 @@ return { parsed } | ||
if (debug) { | ||
_log(`Failed to load ${dotenvPath} ${e.message}`) | ||
_debug(`Failed to load ${dotenvPath} ${e.message}`) | ||
} | ||
@@ -110,9 +222,99 @@ | ||
// Populates process.env from .env file | ||
function config (options) { | ||
const vaultPath = _vaultPath(options) | ||
// fallback to original dotenv if DOTENV_KEY is not set | ||
if (_dotenvKey(options).length === 0) { | ||
return DotenvModule.configDotenv(options) | ||
} | ||
// dotenvKey exists but .env.vault file does not exist | ||
if (!fs.existsSync(vaultPath)) { | ||
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`) | ||
return DotenvModule.configDotenv(options) | ||
} | ||
return DotenvModule._configVault(options) | ||
} | ||
function decrypt (encrypted, keyStr) { | ||
const key = Buffer.from(keyStr.slice(-64), 'hex') | ||
let ciphertext = Buffer.from(encrypted, 'base64') | ||
const nonce = ciphertext.slice(0, 12) | ||
const authTag = ciphertext.slice(-16) | ||
ciphertext = ciphertext.slice(12, -16) | ||
try { | ||
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce) | ||
aesgcm.setAuthTag(authTag) | ||
return `${aesgcm.update(ciphertext)}${aesgcm.final()}` | ||
} catch (error) { | ||
const isRange = error instanceof RangeError | ||
const invalidKeyLength = error.message === 'Invalid key length' | ||
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data' | ||
if (isRange || invalidKeyLength) { | ||
const msg = 'INVALID_DOTENV_KEY: It must be 64 characters long (or more)' | ||
throw new Error(msg) | ||
} else if (decryptionFailed) { | ||
const msg = 'DECRYPTION_FAILED: Please check your DOTENV_KEY' | ||
throw new Error(msg) | ||
} else { | ||
console.error('Error: ', error.code) | ||
console.error('Error: ', error.message) | ||
throw error | ||
} | ||
} | ||
} | ||
// Populate process.env with parsed values | ||
function populate (processEnv, parsed, options = {}) { | ||
const debug = Boolean(options && options.debug) | ||
const override = Boolean(options && options.override) | ||
if (typeof parsed !== 'object') { | ||
throw new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate') | ||
} | ||
// Set process.env | ||
for (const key of Object.keys(parsed)) { | ||
if (Object.prototype.hasOwnProperty.call(processEnv, key)) { | ||
if (override === true) { | ||
processEnv[key] = parsed[key] | ||
} | ||
if (debug) { | ||
if (override === true) { | ||
_debug(`"${key}" is already defined and WAS overwritten`) | ||
} else { | ||
_debug(`"${key}" is already defined and was NOT overwritten`) | ||
} | ||
} | ||
} else { | ||
processEnv[key] = parsed[key] | ||
} | ||
} | ||
} | ||
const DotenvModule = { | ||
configDotenv, | ||
_configVault, | ||
_parseVault, | ||
config, | ||
parse | ||
decrypt, | ||
parse, | ||
populate | ||
} | ||
module.exports.configDotenv = DotenvModule.configDotenv | ||
module.exports._configVault = DotenvModule._configVault | ||
module.exports._parseVault = DotenvModule._parseVault | ||
module.exports.config = DotenvModule.config | ||
module.exports.decrypt = DotenvModule.decrypt | ||
module.exports.parse = DotenvModule.parse | ||
module.exports.populate = DotenvModule.populate | ||
module.exports = DotenvModule |
{ | ||
"name": "dotenv", | ||
"version": "16.0.3", | ||
"version": "16.3.1", | ||
"description": "Loads environment variables from .env file", | ||
@@ -9,4 +9,4 @@ "main": "lib/main.js", | ||
".": { | ||
"types": "./lib/main.d.ts", | ||
"require": "./lib/main.js", | ||
"types": "./lib/main.d.ts", | ||
"default": "./lib/main.js" | ||
@@ -35,2 +35,3 @@ }, | ||
}, | ||
"funding": "https://github.com/motdotla/dotenv?sponsor=1", | ||
"keywords": [ | ||
@@ -48,16 +49,19 @@ "dotenv", | ||
"devDependencies": { | ||
"@types/node": "^17.0.9", | ||
"@definitelytyped/dtslint": "^0.0.133", | ||
"@types/node": "^18.11.3", | ||
"decache": "^4.6.1", | ||
"dtslint": "^3.7.0", | ||
"sinon": "^12.0.1", | ||
"standard": "^16.0.4", | ||
"sinon": "^14.0.1", | ||
"standard": "^17.0.0", | ||
"standard-markdown": "^7.1.0", | ||
"standard-version": "^9.3.2", | ||
"tap": "^15.1.6", | ||
"standard-version": "^9.5.0", | ||
"tap": "^16.3.0", | ||
"tar": "^6.1.11", | ||
"typescript": "^4.5.4" | ||
"typescript": "^4.8.4" | ||
}, | ||
"engines": { | ||
"node": ">=12" | ||
}, | ||
"browser": { | ||
"fs": false | ||
} | ||
} |
279
README.md
@@ -30,14 +30,17 @@ <div align="center"> | ||
</a> | ||
<br> | ||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=dotenv&utm_source=github"> | ||
<div> | ||
<img src="https://res.cloudinary.com/dotenv-org/image/upload/c_scale,w_400/v1665605496/68747470733a2f2f73696e647265736f726875732e636f6d2f6173736574732f7468616e6b732f776f726b6f732d6c6f676f2d77686974652d62672e737667_zdmsbu.svg" width="270" alt="WorkOS"> | ||
</div> | ||
<b>Your App, Enterprise Ready.</b> | ||
<div> | ||
<sup>Add Single Sign-On, Multi-Factor Auth, and more, in minutes instead of months.</sup> | ||
</div> | ||
</a> | ||
<hr> | ||
<br> | ||
<br> | ||
<br> | ||
<br> | ||
</div> | ||
[![dotenv-vault](https://badge.dotenv.org/works-with.svg?r=1)](https://www.dotenv.org/r/github.com/dotenv-org/dotenv-vault?r=1) | ||
# dotenv [![NPM version](https://img.shields.io/npm/v/dotenv.svg?style=flat-square)](https://www.npmjs.com/package/dotenv) | ||
# dotenv | ||
<img src="https://raw.githubusercontent.com/motdotla/dotenv/master/dotenv.svg" alt="dotenv" align="right" width="200" /> | ||
@@ -47,15 +50,17 @@ | ||
[![BuildStatus](https://img.shields.io/travis/motdotla/dotenv/master.svg?style=flat-square)](https://travis-ci.org/motdotla/dotenv) | ||
[![Build status](https://ci.appveyor.com/api/projects/status/github/motdotla/dotenv?svg=true)](https://ci.appveyor.com/project/motdotla/dotenv/branch/master) | ||
[![NPM version](https://img.shields.io/npm/v/dotenv.svg?style=flat-square)](https://www.npmjs.com/package/dotenv) | ||
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) | ||
[![Coverage Status](https://img.shields.io/coveralls/motdotla/dotenv/master.svg?style=flat-square)](https://coveralls.io/github/motdotla/dotenv?branch=coverall-intergration) | ||
[![LICENSE](https://img.shields.io/github/license/motdotla/dotenv.svg)](LICENSE) | ||
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) | ||
[![Featured on Openbase](https://badges.openbase.com/js/featured/dotenv.svg?token=eE0hWPkhC2JGSD4G9hwg5C54EBxjJAyvurGfQsYoKiQ=)](https://openbase.com/js/dotenv?utm_source=embedded&utm_medium=badge&utm_campaign=featured-badge&utm_term=js/dotenv) | ||
[![Limited Edition Tee Original](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Original-yellow?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/original) | ||
[![Limited Edition Tee Redacted](https://img.shields.io/badge/Limited%20Edition%20Tee%20%F0%9F%91%95-Redacted-gray?labelColor=black&style=plastic)](https://dotenv.gumroad.com/l/redacted) | ||
[![dotenv-vault](https://badge.dotenv.org/works-with.svg?r=1)](https://www.dotenv.org/r/github.com/dotenv-org/dotenv-vault?r=1) | ||
## Install | ||
* [π± Install](#-install) | ||
* [ποΈ Usage (.env)](#%EF%B8%8F-usage) | ||
* [π Deploying (.env.vault) π](#-deploying) | ||
* [π΄ Multiple Environments π](#-manage-multiple-environments) | ||
* [π Examples](#-examples) | ||
* [π Docs](#-documentation) | ||
* [β FAQ](#-faq) | ||
* [β±οΈ Changelog](./CHANGELOG.md) | ||
## π± Install | ||
```bash | ||
@@ -68,4 +73,11 @@ # install locally (recommended) | ||
## Usage | ||
## ποΈ Usage | ||
<a href="https://www.youtube.com/watch?v=YtkZR0NFd1g"> | ||
<div align="right"> | ||
<img src="https://img.youtube.com/vi/YtkZR0NFd1g/hqdefault.jpg" alt="how to use dotenv video tutorial" align="right" width="330" /> | ||
<img src="https://simpleicons.vercel.app/youtube/ff0000" alt="youtube/@dotenvorg" align="right" width="24" /> | ||
</div> | ||
</a> | ||
Create a `.env` file in the root of your project: | ||
@@ -85,8 +97,6 @@ | ||
.. or using ES6? | ||
.. [or using ES6?](#how-do-i-use-dotenv-with-import) | ||
```javascript | ||
import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import | ||
dotenv.config() | ||
import express from 'express' | ||
import 'dotenv/config' | ||
``` | ||
@@ -177,4 +187,59 @@ | ||
## Examples | ||
### Deploying | ||
You need to deploy your secrets in a cloud-agnostic manner? Use a `.env.vault` file. | ||
### Multiple Environments | ||
You need to manage your secrets across different environments and apply them as needed? Use a `.env.vault` file with a `DOTENV_KEY`. | ||
## π Deploying | ||
*Note: Requires dotenv >= 16.1.0* | ||
Encrypt your `.env.vault` file. | ||
```bash | ||
$ npx dotenv-vault build | ||
``` | ||
Fetch your production `DOTENV_KEY`. | ||
```bash | ||
$ npx dotenv-vault keys production | ||
``` | ||
Set `DOTENV_KEY` on your server. | ||
```bash | ||
# heroku example | ||
heroku config:set DOTENV_KEY=dotenv://:key_1234β¦@dotenv.org/vault/.env.vault?environment=production | ||
``` | ||
That's it! On deploy, your `.env.vault` file will be decrypted and its secrets injected as environment variables β just in time. | ||
*βΉοΈ A note from [Mot](https://github.com/motdotla): Until recently, we did not have an opinion on how and where to store your secrets in production. We now strongly recommend generating a `.env.vault` file. It's the best way to prevent your secrets from being scattered across multiple servers and cloud providers β protecting you from breaches like the [CircleCI breach](https://techcrunch.com/2023/01/05/circleci-breach/). Also it unlocks interoperability WITHOUT native third-party integrations. Third-party integrations are [increasingly risky](https://coderpad.io/blog/development/heroku-github-breach/) to our industry. They may be the 'du jour' of today, but we imagine a better future.* | ||
<a href="https://github.com/dotenv-org/dotenv-vault#dotenv-vault-">Learn more at dotenv-vault: Deploying</a> | ||
## π΄ Manage Multiple Environments | ||
Edit your production environment variables. | ||
```bash | ||
$ npx dotenv-vault open production | ||
``` | ||
Regenerate your `.env.vault` file. | ||
```bash | ||
$ npx dotenv-vault build | ||
``` | ||
*βΉοΈ π Vault Managed vs π» Locally Managed: The above example, for brevity's sake, used the π Vault Managed solution to manage your `.env.vault` file. You can instead use the π» Locally Managed solution. [Read more here](https://github.com/dotenv-org/dotenv-vault#how-do-i-use--locally-managed-dotenv-vault). Our vision is that other platforms and orchestration tools adopt the `.env.vault` standard as they did the `.env` standard. We don't expect to be the only ones providing tooling to manage and generate `.env.vault` files.* | ||
<a href="https://github.com/dotenv-org/dotenv-vault#-manage-multiple-environments">Learn more at dotenv-vault: Manage Multiple Environments</a> | ||
## π Examples | ||
See [examples](https://github.com/dotenv-org/examples) of using dotenv with various frameworks, languages, and configurations. | ||
@@ -185,2 +250,4 @@ | ||
* [nodejs (override on)](https://github.com/dotenv-org/examples/tree/master/dotenv-nodejs-override) | ||
* [nodejs (processEnv override)](https://github.com/dotenv-org/examples/tree/master/dotenv-custom-target) | ||
* [nodejs (DOTENV_KEY override)](https://github.com/dotenv-org/examples/tree/master/dotenv-vault-custom-target) | ||
* [esm](https://github.com/dotenv-org/examples/tree/master/dotenv-esm) | ||
@@ -197,9 +264,12 @@ * [esm (preload)](https://github.com/dotenv-org/examples/tree/master/dotenv-esm-preload) | ||
* [nestjs](https://github.com/dotenv-org/examples/tree/master/dotenv-nestjs) | ||
* [fastify](https://github.com/dotenv-org/examples/tree/master/dotenv-fastify) | ||
## Documentation | ||
## π Documentation | ||
Dotenv exposes two functions: | ||
Dotenv exposes four functions: | ||
* `config` | ||
* `parse` | ||
* `populate` | ||
* `decrypt` | ||
@@ -226,3 +296,3 @@ ### Config | ||
##### Path | ||
##### path | ||
@@ -237,3 +307,3 @@ Default: `path.resolve(process.cwd(), '.env')` | ||
##### Encoding | ||
##### encoding | ||
@@ -248,3 +318,3 @@ Default: `utf8` | ||
##### Debug | ||
##### debug | ||
@@ -259,3 +329,3 @@ Default: `false` | ||
##### Override | ||
##### override | ||
@@ -270,2 +340,26 @@ Default: `false` | ||
##### processEnv | ||
Default: `process.env` | ||
Specify an object to write your secrets to. Defaults to `process.env` environment variables. | ||
```js | ||
const myObject = {} | ||
require('dotenv').config({ processEnv: myObject }) | ||
console.log(myObject) // values from .env or .env.vault live here now. | ||
console.log(process.env) // this was not changed or written to | ||
``` | ||
##### DOTENV_KEY | ||
Default: `process.env.DOTENV_KEY` | ||
Pass the `DOTENV_KEY` directly to config options. Defaults to looking for `process.env.DOTENV_KEY` environment variable. Note this only applies to decrypting `.env.vault` files. If passed as null or undefined, or not passed at all, dotenv falls back to its traditional job of parsing a `.env` file. | ||
```js | ||
require('dotenv').config({ DOTENV_KEY: 'dotenv://:key_1234β¦@dotenv.org/vault/.env.vault?environment=production' }) | ||
``` | ||
### Parse | ||
@@ -286,3 +380,3 @@ | ||
##### Debug | ||
##### debug | ||
@@ -301,4 +395,61 @@ Default: `false` | ||
## FAQ | ||
### Populate | ||
The engine which populates the contents of your .env file to `process.env` is available for use. It accepts a target, a source, and options. This is useful for power users who want to supply their own objects. | ||
For example, customizing the source: | ||
```js | ||
const dotenv = require('dotenv') | ||
const parsed = { HELLO: 'world' } | ||
dotenv.populate(process.env, parsed) | ||
console.log(process.env.HELLO) // world | ||
``` | ||
For example, customizing the source AND target: | ||
```js | ||
const dotenv = require('dotenv') | ||
const parsed = { HELLO: 'universe' } | ||
const target = { HELLO: 'world' } // empty object | ||
dotenv.populate(target, parsed, { override: true, debug: true }) | ||
console.log(target) // { HELLO: 'universe' } | ||
``` | ||
#### options | ||
##### Debug | ||
Default: `false` | ||
Turn on logging to help debug why certain keys or values are not being populated as you expect. | ||
##### override | ||
Default: `false` | ||
Override any environment variables that have already been set. | ||
### Decrypt | ||
The engine which decrypts the ciphertext contents of your .env.vault file is available for use. It accepts a ciphertext and a decryption key. It uses AES-256-GCM encryption. | ||
For example, decrypting a simple ciphertext: | ||
```js | ||
const dotenv = require('dotenv') | ||
const ciphertext = 's7NYXa809k/bVSPwIAmJhPJmEGTtU0hG58hOZy7I0ix6y5HP8LsHBsZCYC/gw5DDFy5DgOcyd18R' | ||
const decryptionKey = 'ddcaa26504cd70a6fef9801901c3981538563a1767c297cb8416e8a38c62fe00' | ||
const decrypted = dotenv.decrypt(ciphertext, decryptionKey) | ||
console.log(decrypted) // # development@v6\nALPHA="zeta" | ||
``` | ||
## β FAQ | ||
### Why is the `.env` file not loading my environment variables successfully? | ||
@@ -388,4 +539,3 @@ | ||
// index.mjs (ESM) | ||
import * as dotenv from 'dotenv' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import | ||
dotenv.config() | ||
import 'dotenv/config' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import | ||
import express from 'express' | ||
@@ -402,10 +552,12 @@ ``` | ||
`errorReporter.mjs`: | ||
```js | ||
// errorReporter.mjs | ||
import { Client } from 'best-error-reporting-service' | ||
export default new Client(process.env.API_KEY) | ||
// index.mjs | ||
import dotenv from 'dotenv' | ||
``` | ||
`index.mjs`: | ||
```js | ||
// Note: this is INCORRECT and will not work | ||
import * as dotenv from 'dotenv' | ||
dotenv.config() | ||
@@ -419,14 +571,7 @@ | ||
Instead the above code should be written as.. | ||
Instead, `index.mjs` should be written as.. | ||
```js | ||
// errorReporter.mjs | ||
import { Client } from 'best-error-reporting-service' | ||
import 'dotenv/config' | ||
export default new Client(process.env.API_KEY) | ||
// index.mjs | ||
import * as dotenv from 'dotenv' | ||
dotenv.config() | ||
import errorReporter from './errorReporter.mjs' | ||
@@ -443,2 +588,40 @@ errorReporter.report(new Error('documented example')) | ||
### Why am I getting the error `Module not found: Error: Can't resolve 'crypto|os|path'`? | ||
You are using dotenv on the front-end and have not included a polyfill. Webpack < 5 used to include these for you. Do the following: | ||
```bash | ||
npm install node-polyfill-webpack-plugin | ||
``` | ||
Configure your `webpack.config.js` to something like the following. | ||
```js | ||
require('dotenv').config() | ||
const path = require('path'); | ||
const webpack = require('webpack') | ||
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin') | ||
module.exports = { | ||
mode: 'development', | ||
entry: './src/index.ts', | ||
output: { | ||
filename: 'bundle.js', | ||
path: path.resolve(__dirname, 'dist'), | ||
}, | ||
plugins: [ | ||
new NodePolyfillPlugin(), | ||
new webpack.DefinePlugin({ | ||
'process.env': { | ||
HELLO: JSON.stringify(process.env.HELLO) | ||
} | ||
}), | ||
] | ||
}; | ||
``` | ||
Alternatively, just use [dotenv-webpack](https://github.com/mrsteele/dotenv-webpack) which does this and more behind the scenes for you. | ||
### What about variable expansion? | ||
@@ -452,2 +635,6 @@ | ||
### What is a `.env.vault` file? | ||
A `.env.vault` file is an encrypted version of your development (and ci, staging, production, etc) environment variables. It is paired with a `DOTENV_KEY` to deploy your secrets more securely than scattering them across multiple platforms and tools. Use [dotenv-vault](https://github.com/dotenv-org/dotenv-vault) to manage and generate them. | ||
## Contributing Guide | ||
@@ -454,0 +641,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
71560
11
426
634
6