diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c87662..7d363e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.1.0 +- **Feature**: Added support for `key value` mapping in env vars file +- **Feature**: Added support for inline comments `ENV=VALUE # inline comment` +- **Change**: Will now ignore invalid lines in env vars file instead of throwing an error +- **Change**: Migrated all the parsing over to regex since the file format is simple enough right +now to support that +- **Bug**: Removed old test cases for the `-e/--env` flags that were not needed anymore + ## 2.0.0 - ***BREAKING***: Removed the `-e` and `--env` flags. Now it just expects the first arg to `env-cmd` to be the relative path to the env file: `env-cmd env_file command carg1 carg2` - **Change:** `ParseEnvFile` is now more properly named `ParseEnvString` diff --git a/README.md b/README.md index d07fb29..517dd63 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![npm](https://img.shields.io/npm/l/env-cmd.svg?maxAge=2592000)](https://www.npmjs.com/package/env-cmd) # env-cmd -A simple node program for executing commands using an environment from an env file +A simple node program for executing commands using an environment from an env file. ## Install `npm install env-cmd` @@ -14,14 +14,13 @@ A simple node program for executing commands using an environment from an env fi **Environment file `./test/.env`** ``` # This is a comment -ENV1=THANKS -ENV2=FORALL -ENV4=THEFISH +ENV1=THANKS # Yay inline comments support +ENV2=FOR ALL +ENV3 THE FISH # This format is also accepted ``` -*This is the only accepted format for an environment file. If other formats are desired please create an issue* **Package.json** -```js +```json { "scripts": { "test": "env-cmd ./test/.env mocha -R spec" @@ -35,11 +34,17 @@ or ./node_modules/.bin/env-cmd ./test/.env node index.js ``` +## Environment File Formats + +These are the currently accepted environment file formats. If any other formats are desired please create an issue. +- `key=value` +- `key value` + ## Why Because sometimes its just too cumbersome passing lots of environment variables to scripts. Its usually just easier to have a file with all the vars in them, especially for development and testing. -**Do not commit sensitive env data to a public git repo!** +**Do not commit sensitive environment data to a public git repo!** ## Related Projects diff --git a/lib/index.js b/lib/index.js index a7b6c36..f34b10c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -58,37 +58,39 @@ function ParseArgs (args) { } } -function ParseEnvString (envFileString) { - const envs = Object.assign({}, process.env) - while (envFileString.length) { - // The the last index of the line using the newline delimiter - let endOfLineIndex = envFileString.indexOf('\n') +function StripComments (envString) { + const commentsRegex = /[ ]*(#.*$)/gim + return envString.replace(commentsRegex, '') +} - // If no newline, then assume end of file - if (endOfLineIndex === -1) { - endOfLineIndex = envFileString.length - } +function StripEmptyLines (envString) { + const emptyLinesRegex = /(^\n)/gim + return envString.replace(emptyLinesRegex, '') +} - // Get the full line - const line = envFileString.slice(0, endOfLineIndex + 1) - - // Shrink the file by 1 line - envFileString = envFileString.slice(line.length) - - // Only parse lines that are not empty and don't begin with # - if (line.length > 1 && line[0] !== '#') { - // Parse the line - const equalSign = line.indexOf('=') - - if (equalSign === -1) { - throw new Error('Error! Malformed line in env file.') - } - - // Set then new env var - envs[line.slice(0, equalSign)] = line.slice(equalSign + 1, endOfLineIndex) - } +function ParseEnvVars (envString) { + const envParseRegex = /^((.+?)[ =](.*))$/gim + const matches = {} + let match + while ((match = envParseRegex.exec(envString)) !== null) { + // Note: match[1] is the full env=var line + matches[match[2]] = match[3] } - return envs + return matches +} + +function ParseEnvString (envFileString) { + // First thing we do is stripe out all comments + envFileString = StripComments(envFileString) + + // Next we stripe out all the empty lines + envFileString = StripEmptyLines(envFileString) + + // Parse the envs vars out + const envs = ParseEnvVars(envFileString) + + // Merge the file env vars with the current process env vars (the file vars overwrite process vars) + return Object.assign({}, process.env, envs) } function PrintHelp () { @@ -114,5 +116,8 @@ module.exports = { ParseArgs, ParseEnvString, PrintHelp, - HandleUncaughtExceptions + HandleUncaughtExceptions, + StripComments, + StripEmptyLines, + ParseEnvVars } diff --git a/package.json b/package.json index 47f7236..2d3b50c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "env-cmd", - "version": "2.0.0", + "version": "2.1.0", "description": "Executes a command using the envs in the provided env file", "main": "lib/index.js", "bin": { diff --git a/test/test.js b/test/test.js index 8ea7d19..283ec77 100644 --- a/test/test.js +++ b/test/test.js @@ -27,15 +27,13 @@ const ParseArgs = lib.ParseArgs const ParseEnvString = lib.ParseEnvString const PrintHelp = lib.PrintHelp const HandleUncaughtExceptions = lib.HandleUncaughtExceptions +const StripComments = lib.StripComments +const StripEmptyLines = lib.StripEmptyLines +const ParseEnvVars = lib.ParseEnvVars describe('env-cmd', function () { describe('ParseArgs', function () { - it('should parse out the -e envfile path', function () { - const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2']) - assert(parsedArgs.envFilePath === path.join(__dirname, 'envFile')) - }) - - it('should parse out the --env envfile path', function () { + it('should parse out the envfile path', function () { const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2']) assert(parsedArgs.envFilePath === path.join(__dirname, 'envFile')) }) @@ -64,35 +62,70 @@ describe('env-cmd', function () { }) describe('ParseEnvString', function () { - it('should parse out vars in the environment variable string', function () { + it('should parse env vars and merge (overwrite) with process.env vars', function () { + process.env.TEST = 'SOME TEST VAR' + process.env.NODE_ENV = 'development' const env = ParseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n') assert(env.BOB === 'COOL') assert(env.NODE_ENV === 'dev') assert(env.ANSWER === '42') + assert(env.TEST === 'SOME TEST VAR') + }) + }) + + describe('StripComments', function () { + it('should strip out all full line comments', 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 ignore comment lines (starting with \'#\') and empty lines', function () { - const env = ParseEnvString('#BOB=COOL\nNODE_ENV=dev\n\n#ANSWER=42\n') - assert(env.BOB === undefined) - assert(env.NODE_ENV === 'dev') - assert(env.ANSWER === undefined) + 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') + }) + }) + + describe('StripEmptyLines', function () { + it('should strip out all empty lines', function () { + const envString = StripEmptyLines('\nBOB=COOL\n\nNODE_ENV=dev\n\nANSWER=42 AND COUNTING\n\n') + assert(envString === 'BOB=COOL\nNODE_ENV=dev\nANSWER=42 AND COUNTING\n') + }) + }) + + 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') + assert(envVars.BOB === 'COOL') + assert(envVars.NODE_ENV === 'dev') + assert(envVars.ANSWER === '42 AND COUNTING') }) - it('should parse out env vars even if string does not end in \'\\n\'', function () { - const env = ParseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42') - assert(env.BOB === 'COOL') - assert(env.NODE_ENV === 'dev') - assert(env.ANSWER === '42') + 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 throw parse error due to malformed env var string', function () { - try { - ParseEnvString('BOB=COOL\nNODE_ENV dev\nANSWER=42\n') - } catch (e) { - assert(e.message === 'Error! Malformed line in env file.') - return - } - assert(!'No exception thrown') + 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) + assert(envVars.BOB === 'COOL') + assert(envVars.ANSWER === '42 AND COUNTING') }) })