diff --git a/README.md b/README.md index 67396d4..9025eb4 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,44 @@ A simple node program for executing commands using an environment from an env fi ## Install `npm install env-cmd` or `npm install -g env-cmd` -## Usage - -### Environment File Usage - -If the specified environment file can't be found, an error message is logged. -If then `.env` fallback file can't be found too, an error is thrown. +## Basic Usage **Environment file `./test/.env`** ``` # This is a comment -ENV1=THANKS # Yay inline comments support +ENV1=THANKS ENV2=FOR ALL -ENV3 THE FISH # This format is also accepted +ENV3=THE FISH +``` -# Surround value in double quotes when using a # symbol in the value -ENV4="ValueContains#Symbol" +**Package.json** +```json +{ + "scripts": { + "test": "env-cmd ./test/.env mocha -R spec" + } +} +``` +or -# If using double quotes as part of the value, you must surround the value in double quotes -ENV5=""Value includes double quotes"" +**Terminal** +```sh +# uses ./test/.env +./node_modules/.bin/env-cmd ./test/.env node index.js +``` + +## Advanced Usage + +### Fallback file usage + +You can specify an `.env.local` (or any name) env file, add that to your `.gitignore` and use that in your local development environment. Then you can use a regular `.env` file in root directory with production configs that can get committed to a private/protected repo. When `env-cmd` cannot find the `.env.local` file it will fallback to looking for a regular `.env` file. + +**Environment file `./.env.local`** +``` +# This is a comment +ENV1=THANKS +ENV2=FOR ALL +ENV3=THE FISH ``` **Fallback Environment file `./.env`** ``` @@ -42,20 +61,11 @@ ENV5=gorge ``` **Package.json** -to use `./test/.env` -```json -{ - "scripts": { - "test": "env-cmd ./test/.env mocha -R spec" - } -} -``` - uses `./.env` as a fallback ```json { "scripts": { - "test": "env-cmd ./test/.doesntExist mocha -R spec" + "test": "env-cmd ./.env.local mocha -R spec" } } ``` @@ -63,15 +73,14 @@ or **Terminal** ```sh -# uses ./test/.env -./node_modules/.bin/env-cmd ./test/.env node index.js -# uses ./.env as a fallback, because i can't find `./test/.myEnv` -./node_modules/.bin/env-cmd ./test/.myEnv node index.js +# uses ./.env as a fallback, because it can't find `./.env.local` +./node_modules/.bin/env-cmd ./.env.local node index.js ``` - ### .rc file usage +For more complex projects, a `.env-cmdrc` file can be defined in the root directory and supports as many environments as you want. Instead of passing the path to a `.env` file to `env-cmd`, simple pass the name of the environment you want use thats in your `.env-cmdrc` file. + **.rc file `.env-cmdrc`** ```json @@ -95,7 +104,6 @@ or These are the currently accepted environment file formats. If any other formats are desired please create an issue. - `key=value` -- `key value` - Key/value pairs as JSON - JavaScript file exporting an object - `.env-cmdrc` file (as valid json) in execution directory @@ -118,6 +126,7 @@ Special thanks to [`cross-env`](https://github.com/kentcdodds/cross-env) for ins - Eric Lanehart - Jon Scheiding +- Alexander Praetorius ## Contributing Guide I welcome all pull requests. Please make sure you add appropriate test cases for any features added. Before opening a PR please make sure to run the following scripts: diff --git a/lib/index.js b/lib/index.js index 426d5a4..8b23dc0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -56,11 +56,11 @@ function ParseArgs (args) { // Strips out comments from env file string function StripComments (envString) { - const commentsRegex = /("{1}.*"{1})*?([ ]*#.*$)/gim + const commentsRegex = /(^#.*$)/gim let match = commentsRegex.exec(envString) let newString = envString while (match != null) { - newString = newString.replace(match[2], '') + newString = newString.replace(match[1], '') match = commentsRegex.exec(envString) } return newString @@ -72,21 +72,9 @@ function StripEmptyLines (envString) { return envString.replace(emptyLinesRegex, '') } -// Stripes out double quotes to allow for usage for special # in values -function StripDoubleQuotes (envString) { - const doubleQuotesRegex = /"{1}(.*)"{1}/gim - let match = doubleQuotesRegex.exec(envString) - let newString = envString - while (match != null) { - newString = newString.replace(match[0], match[1]) - match = doubleQuotesRegex.exec(envString) - } - return newString -} - // Parse out all env vars from an env file string function ParseEnvVars (envString) { - const envParseRegex = /^((.+?)[ =](.*))$/gim + const envParseRegex = /^((.+?)[=](.*))$/gim const matches = {} let match while ((match = envParseRegex.exec(envString)) !== null) { @@ -104,9 +92,6 @@ function ParseEnvString (envFileString) { // Next we stripe out all the empty lines envFileString = StripEmptyLines(envFileString) - // Finally we stripe out all the double quotes for special charactes - envFileString = StripDoubleQuotes(envFileString) - // Parse the envs vars out const envs = ParseEnvVars(envFileString) @@ -123,7 +108,16 @@ function ParseRCFile (fileData) { function UseRCFile (parsedArgs) { const fileData = fs.readFileSync(rcFileLocation, { encoding: 'utf8' }) const parsedData = ParseRCFile(fileData) - return parsedData[parsedArgs.envFile] + const envVars = parsedData[parsedArgs.envFile] + if (!envVars) { + console.error(`Error: + Could not find environment: + ${parsedArgs.envFile} + in .rc file: + ${rcFileLocation}`) + throw new Error(`Missing environment ${parsedArgs.envFile} in .env-cmdrc file.`) + } + return envVars } // Uses the cli passed env file to get env vars @@ -141,6 +135,10 @@ function UseCmdLine (parsedArgs) { Trying to fallback to read: ${envFilePathDefault} `) + } + + // If we don't have a main file try the fallback file + if (!file) { try { file = fs.readFileSync(envFilePathDefault) } catch (e) { @@ -148,6 +146,7 @@ function UseCmdLine (parsedArgs) { } } + // Get the file extension const ext = path.extname(envFilePath).toLowerCase() // Parse the env file string using the correct parser @@ -188,7 +187,6 @@ module.exports = { HandleUncaughtExceptions, StripComments, StripEmptyLines, - StripDoubleQuotes, ParseEnvVars, ParseRCFile, UseRCFile, diff --git a/test/test.js b/test/test.js index 63d74eb..d01addd 100644 --- a/test/test.js +++ b/test/test.js @@ -40,7 +40,6 @@ const PrintHelp = lib.PrintHelp const HandleUncaughtExceptions = lib.HandleUncaughtExceptions const StripComments = lib.StripComments const StripEmptyLines = lib.StripEmptyLines -const StripDoubleQuotes = lib.StripDoubleQuotes const ParseEnvVars = lib.ParseEnvVars describe('env-cmd', function () { @@ -90,16 +89,6 @@ describe('env-cmd', function () { const envString = StripComments('#BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n#AnotherComment\n') assert(envString === '\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n\n') }) - - it('should strip out all inline comments and preceding spaces', function () { - const envString = StripComments('BOB=COOL#inline1\nNODE_ENV=dev #cool\nANSWER=42 AND COUNTING #multiple-spaces\n') - assert(envString === 'BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n') - }) - - it('should not strip out values that are surrouned in double quotes', function () { - const envString = StripComments('BOB="#COOL"#inline1\nNODE_ENV=dev #cool\nANSWER="#42 AND COUNTING" #multiple-spaces\n') - assert(envString === 'BOB="#COOL"\nNODE_ENV=dev\nANSWER="#42 AND COUNTING"\n') - }) }) describe('StripEmptyLines', function () { @@ -109,13 +98,6 @@ describe('env-cmd', function () { }) }) - describe('StripDoubleQuotes', function () { - it('should strip out single double quotes', function () { - const envString = StripDoubleQuotes(`BOB="#COOL"\nNODE_ENV=dev\nANSWER="42 AND #COUNTING"\nCOOL=""quoted""\nAwesome="'singles'"`) - assert(envString === `BOB=#COOL\nNODE_ENV=dev\nANSWER=42 AND #COUNTING\nCOOL="quoted"\nAwesome='singles'`) - }) - }) - describe('ParseEnvVars', function () { it('should parse out all env vars in string when not ending with \'\\n\'', function () { const envVars = ParseEnvVars('BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING') @@ -131,20 +113,6 @@ describe('env-cmd', function () { assert(envVars.ANSWER === '42 AND COUNTING') }) - it('should parse out all env vars in string with format \'key value\'', function () { - const envVars = ParseEnvVars('BOB COOL\nNODE_ENV dev\nANSWER 42 AND COUNTING\n') - assert(envVars.BOB === 'COOL') - assert(envVars.NODE_ENV === 'dev') - assert(envVars.ANSWER === '42 AND COUNTING') - }) - - it('should parse out all env vars in string with mixed format \'key=value\' & \'key value\'', function () { - const envVars = ParseEnvVars('BOB=COOL\nNODE_ENV dev\nANSWER=42 AND COUNTING\n') - assert(envVars.BOB === 'COOL') - assert(envVars.NODE_ENV === 'dev') - assert(envVars.ANSWER === '42 AND COUNTING') - }) - it('should ignore invalid lines', function () { const envVars = ParseEnvVars('BOB=COOL\nTHISIS$ANDINVALIDLINE\nANSWER=42 AND COUNTING\n') assert(Object.keys(envVars).length === 2) @@ -218,6 +186,7 @@ describe('env-cmd', function () { assert(spawnStub.args[0][2].env.NODE_ENV === 'dev') assert(spawnStub.args[0][2].env.ANSWER === '42') }) + it('should parse env vars from .env-cmdrc file using production env', function () { EnvCmd(['production', 'echo', '$BOB']) assert(spawnStub.args[0][0] === 'echo') @@ -226,6 +195,16 @@ describe('env-cmd', function () { assert(spawnStub.args[0][2].env.NODE_ENV === 'prod') assert(spawnStub.args[0][2].env.ANSWER === '43') }) + + it('should throw error if env not in .rc file', function () { + try { + EnvCmd(['staging', 'echo', '$BOB']) + assert(!'Should throw missing environment error.') + } catch (e) { + assert(e.message.includes('staging')) + assert(e.message.includes(`.env-cmdrc`)) + } + }) }) describe('EnvCmd', function () {