dotenv multi logic

This commit is contained in:
RingOfStorms (Joshua Bell) 2020-12-19 18:33:47 -06:00
parent 5582d09293
commit 060163f64a
11 changed files with 200 additions and 94 deletions

40
.gitignore vendored Normal file
View file

@ -0,0 +1,40 @@
# compiled output
/dist
/lib
/node_modules
*.tgz
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# .env local files
.env.development.local
.env.local
.env.production.local
.env.test.local

View file

@ -1,14 +1,21 @@
# dotenv-multi
Dotenv-multi is a module that uses `dotenv` and `dotenv-expand` to load files similarly to Create React App script's. It allows the use of NODE_ENV and local specific env files.
Dotenv-multi is a module that uses [dotenv](https://www.npmjs.com/package/dotenv) and [dotenv-expand](https://www.npmjs.com/package/dotenv-expand) to load files similarly to [Create React App script's](https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used). It allows the use of NODE_ENV and local specific env files.
This is useful because now you can commit your default env files into the repo and still have a sane way to override them locally without having changes in your local git on one .env file.
Example root files:
```
.env
.env.development
.env.development.local (not pushed to git)
```
# Install
```
# with npm
npm install @ringofstorms/dotenv-multi dotenv dotenv-expand
npm install @ringofstorms/dotenv-multi
```
# Usage
@ -25,6 +32,27 @@ import { config } from '@ringofstorms/multi-env';
config();
```
## Boolean support
This package also uses the [yn](https://www.npmjs.com/package/yn) package to convert environment variables into javascript strings that will resolve as true false. (It replaces values like `0`, `no`, `false`, etc into an empty string `""`)
```typescript
config({
convertToBooleanPredicate: variable => variable.includes("BOOL"),
/* and or */ convertToBoolean: ["BOOL_VARIABLE_TO_CONVERT"]
});
```
## Debug
You can enable debug logs which will also enable debug mode on `dotenv` calls to see what is going on when a variable does not resolve how you expect.
```typescript
config({
debug: true,
});
```
# Configuring .gitignore
You should add the following to your `.gitignore` file:
@ -36,4 +64,14 @@ You should add the following to your `.gitignore` file:
.env.test.local
```
## [What other `.env` files can be used?](https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used)
# Contributing
### building
`npm run build`
### publishing
if not already done `npm login`
`npm publish --access public`

1
dist/index.d.ts vendored
View file

@ -1 +0,0 @@
export declare function config(): Record<string, string>;

9
dist/index.js vendored
View file

@ -1,9 +0,0 @@
"use strict";
exports.__esModule = true;
exports.config = void 0;
function config() {
console.log("TEST config");
return { test: 'test' };
}
exports.config = config;
//# sourceMappingURL=index.js.map

1
dist/index.js.map vendored
View file

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,SAAgB,MAAM;IACpB,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAHD,wBAGC"}

View file

@ -1,63 +0,0 @@
{
"program": {
"fileInfos": {
"../node_modules/typescript/lib/lib.d.ts": {
"version": "2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",
"signature": "2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",
"affectsGlobalScope": false
},
"../node_modules/typescript/lib/lib.es5.d.ts": {
"version": "9622e8bd7cc72a7dab819a8011ecbf81d443638082e5cb99ecf2e75ff56ffc9d",
"signature": "9622e8bd7cc72a7dab819a8011ecbf81d443638082e5cb99ecf2e75ff56ffc9d",
"affectsGlobalScope": true
},
"../node_modules/typescript/lib/lib.dom.d.ts": {
"version": "abadddbf660adeec27e9a56584907d52fa1d6e1e1dc49f639a921baa951b7a84",
"signature": "abadddbf660adeec27e9a56584907d52fa1d6e1e1dc49f639a921baa951b7a84",
"affectsGlobalScope": true
},
"../node_modules/typescript/lib/lib.webworker.importscripts.d.ts": {
"version": "7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481",
"signature": "7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481",
"affectsGlobalScope": true
},
"../node_modules/typescript/lib/lib.scripthost.d.ts": {
"version": "097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd",
"signature": "097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd",
"affectsGlobalScope": true
},
"../src/index.ts": {
"version": "a046922c32012fd8e2d32b30256e7ae8eaf10f6ee52cb4f6a8de3fe75aa534eb",
"signature": "a045457643ba909269c1cec5ec9c110dcfa9242adf17b0bb9676f0f3f5cf82e9",
"affectsGlobalScope": false
}
},
"options": {
"target": 0,
"module": 1,
"strict": true,
"allowJs": false,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"incremental": true,
"outDir": "./",
"baseUrl": "..",
"configFilePath": "../tsconfig.json"
},
"referencedMap": {},
"exportedModulesMap": {},
"semanticDiagnosticsPerFile": [
"../node_modules/typescript/lib/lib.d.ts",
"../node_modules/typescript/lib/lib.dom.d.ts",
"../node_modules/typescript/lib/lib.es5.d.ts",
"../node_modules/typescript/lib/lib.scripthost.d.ts",
"../node_modules/typescript/lib/lib.webworker.importscripts.d.ts",
"../src/index.ts"
]
},
"version": "4.1.3"
}

25
package-lock.json generated
View file

@ -1,9 +1,15 @@
{
"name": "@ringofstorms/dotenv",
"version": "0.1.0",
"name": "@ringofstorms/dotenv-multi",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "14.14.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz",
"integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==",
"dev": true
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -26,6 +32,16 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"dotenv-expand": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -106,6 +122,11 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"yn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz",
"integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg=="
}
}
}

View file

@ -1,21 +1,26 @@
{
"name": "@ringofstorms/dotenv",
"name": "@ringofstorms/dotenv-multi",
"version": "0.1.0",
"description": "Dot environment file loader that acts like Create React App's supporting many files with per NODE_ENV/local settings.",
"main": "index.js",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib"
],
"scripts": {
"prebuild": "npm run clean",
"build": "tsc",
"clean": "rimraf dist",
"test": "echo \"Error: no test specified\" && exit 1"
"clean": "rimraf lib *.tgz"
},
"author": "",
"author": "Josh (RingOfStorms)",
"license": "ISC",
"peerDependencies": {
"dotenv": "^8.x",
"dotenv-expand": "^5.x"
"dependencies": {
"dotenv": "^8.2.0",
"dotenv-expand": "^5.1.0",
"yn": "^4.0.0"
},
"devDependencies": {
"@types/node": "^14.14.14",
"rimraf": "^3.0.2",
"typescript": "^4.1.3"
}

View file

@ -1,4 +1,69 @@
export function config(): Record<string, string> {
console.log("TEST config");
return { test: 'test' };
import * as fs from 'fs';
import * as path from 'path';
import * as dotenv from 'dotenv';
import * as dotenvExpand from 'dotenv-expand';
import * as yn from 'yn';
export interface ConfigOptions {
convertToBooleanPredicate?: (variable: string) => boolean;
convertToBoolean?: string | string[];
debug?: boolean;
}
export function config(options: ConfigOptions = {}): void {
const log = options.debug ? console.log : (() => {});
// Same logic as create-react-app for resolving env files
// https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/env.js
const NODE_ENV = (process.env.NODE_ENV || 'unknown').toLowerCase();
const appDirectory = fs.realpathSync(process.cwd());
const dotenvFilePath: string = path.resolve(appDirectory, '.env');
const dotenvFiles = [
`${ dotenvFilePath }.${ NODE_ENV }.local`,
`${ dotenvFilePath }.${ NODE_ENV }`,
NODE_ENV !== 'test' && `${ dotenvFilePath }.local`,
dotenvFilePath,
].filter(i => i) as string[]; // this cast is safe due to the identity filter
log('Environment files to check:', dotenvFiles.join());
const propsConfigured = {};
dotenvFiles.forEach((dotenvFile) => {
if (fs.existsSync(dotenvFile)) {
const {parsed} = dotenvExpand(dotenv.config({path: dotenvFile, debug: options.debug || undefined }));
Object.assign(propsConfigured, parsed);
}
});
log('Environment variables loaded from dotenvs:', Object.keys(propsConfigured).length);
log('\t', propsConfigured);
const runBoolConverter: boolean = (options.convertToBooleanPredicate || options.convertToBoolean);
log('Environment variable boolean converter will run:', runBoolConverter());
if (runBoolConverter) {
const convertPredicate = (key: string): boolean => {
if (key) {
if (Array.isArray(options.convertToBoolean)) {
if (options.convertToBoolean.includes(key)) {
return true;
}
} else if (options.convertToBoolean === key) {
return true;
}
if (typeof options.convertToBooleanPredicate === 'function' && options.convertToBooleanPredicate(key)) {
return true;
}
}
return false;
}
Object.entries(process.env).forEach(([key, value]) => {
if (convertPredicate(key)) {
const boolValue = yn(value) ? 'true' : '';
process.env[key] = boolValue;
log('Environment variable converted into string boolean value:', key, "original:", value, 'newValue:', boolValue);
}
});
}
}

View file

@ -1,4 +1,16 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts", "**/*test.ts"]
}
"include": ["src"],
"exclude": [
".github",
"examples",
"lib",
"node_modules",
"output",
"test",
"**/__mocks__/**",
"**/__tests__/**",
"**/*spec.ts",
"**/*test.ts"
]
}

View file

@ -8,10 +8,9 @@
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"incremental": true,
"outDir": "./dist",
"outDir": "./lib",
"baseUrl": "./"
}
}