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
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. 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 # Install
``` ```
# with npm # with npm
npm install @ringofstorms/dotenv-multi dotenv dotenv-expand npm install @ringofstorms/dotenv-multi
``` ```
# Usage # Usage
@ -25,6 +32,27 @@ import { config } from '@ringofstorms/multi-env';
config(); 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 # Configuring .gitignore
You should add the following to your `.gitignore` file: 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 .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", "name": "@ringofstorms/dotenv-multi",
"version": "0.1.0", "version": "0.0.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "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": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -26,6 +32,16 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true "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": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true "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", "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.", "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": { "scripts": {
"prebuild": "npm run clean", "prebuild": "npm run clean",
"build": "tsc", "build": "tsc",
"clean": "rimraf dist", "clean": "rimraf lib *.tgz"
"test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "author": "Josh (RingOfStorms)",
"license": "ISC", "license": "ISC",
"peerDependencies": { "dependencies": {
"dotenv": "^8.x", "dotenv": "^8.2.0",
"dotenv-expand": "^5.x" "dotenv-expand": "^5.1.0",
"yn": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^14.14.14",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.1.3" "typescript": "^4.1.3"
} }

View file

@ -1,4 +1,69 @@
export function config(): Record<string, string> { import * as fs from 'fs';
console.log("TEST config"); import * as path from 'path';
return { test: 'test' };
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", "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, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true, "sourceMap": true,
"incremental": true, "incremental": true,
"outDir": "./dist", "outDir": "./lib",
"baseUrl": "./" "baseUrl": "./"
} }
} }