diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2fa519 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index b80c057..704bc42 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 84284e4..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare function config(): Record; diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index c8a5800..0000000 --- a/dist/index.js +++ /dev/null @@ -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 \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map deleted file mode 100644 index 5018a65..0000000 --- a/dist/index.js.map +++ /dev/null @@ -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"} \ No newline at end of file diff --git a/dist/tsconfig.tsbuildinfo b/dist/tsconfig.tsbuildinfo deleted file mode 100644 index dd7e550..0000000 --- a/dist/tsconfig.tsbuildinfo +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 41088a4..ead1f38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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==" } } } diff --git a/package.json b/package.json index 402873e..8f36875 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/src/index.ts b/src/index.ts index 69eb381..562dd0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,69 @@ -export function config(): Record { - 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); + } + }); + } } diff --git a/tsconfig.build.json b/tsconfig.build.json index a4ec716..5790fe5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -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" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 3ee9f7a..e03fbf9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,10 +8,9 @@ "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, - "allowSyntheticDefaultImports": true, "sourceMap": true, "incremental": true, - "outDir": "./dist", + "outDir": "./lib", "baseUrl": "./" } }