Merge pull request #1 from toddbluhm/2.0.0

Removed -e/--env flags, allow # comment lines in env file, bug fixes
This commit is contained in:
Todd Bluhm 2016-08-17 16:30:14 -05:00 committed by GitHub
commit c81fb43183
8 changed files with 115 additions and 72 deletions

View File

@ -1,4 +1,3 @@
.gitignore
coverage/
node_modules/
test/

View File

@ -1,5 +1,11 @@
# Changelog
## 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`
- Changed ParseEnvFile over to more generic name: ParseEnvString
- ParseEnvString: Ignore comment lines (lines starting with '#')
- ParseEnvString: Ignore empty lines in env file
## 1.0.1
- Fixed badges
- Added .npmignore

View File

@ -12,6 +12,7 @@ A simple node program for executing commands using an environment from an env fi
## Usage
**Environment file ``./test/.env`**
```
# This is a comment
ENV1=THANKS
ENV2=FORALL
ENV4=THEFISH
@ -22,7 +23,7 @@ ENV4=THEFISH
```js
{
"scripts": {
"test": "env-cmd -e ./test/.env mocha -R spec"
"test": "env-cmd ./test/.env mocha -R spec"
}
}
```
@ -30,15 +31,15 @@ or
**Terminal**
```sh
./node_modules/.bin/env-cmd -e ./test/.env node index.js
./node_modules/.bin/env-cmd ./test/.env node index.js
```
## Why
Because sometimes it just too cumbersome passing tons of environment variables to scripts. Its usually just easier to have a file with all the vars in them, especially for development.
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 data to a public git!**
**Do not commit sensitive env data to a public git repo!**
## Special Thanks
Special thanks to [cross-env](https://github.com/kentcdodds/cross-env) for inspiration (use's the same `cross-spawn` lib underneath).
Special thanks to [cross-env](https://github.com/kentcdodds/cross-env) for inspiration (use's the same `cross-spawn` lib underneath too).

View File

@ -5,8 +5,21 @@ const path = require('path')
const fs = require('fs')
function EnvCmd (args) {
// Parse the args from the command line
const parsedArgs = ParseArgs(args)
const env = ParseEnvFile(parsedArgs.envFilePath)
// Attempt to open the provided file
let file
try {
file = fs.readFileSync(parsedArgs.envFilePath, { encoding: 'utf8' })
} catch (e) {
throw new Error(`Error! Could not find or read file at ${parsedArgs.envFilePath}`)
}
// Parse the env file string
const env = ParseEnvString(file)
// Execute the command with the given environment variables
if (parsedArgs.command) {
const proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
stdio: 'inherit',
@ -19,30 +32,25 @@ function EnvCmd (args) {
}
function ParseArgs (args) {
if (args.length < 3) {
if (args.length < 2) {
throw new Error('Error! Too few arguments passed to env-cmd.')
}
const envFileFlags = /(^\-e$|^\-\-env$)/g
let envFilePath
let command
let commandArgs = args.slice()
while (commandArgs.length) {
const arg = commandArgs.shift()
// if this is the env file flag the get the file
if (arg.match(envFileFlags)) {
envFilePath = path.resolve(process.cwd(), commandArgs.shift())
// assume the first arg is the env file
if (!envFilePath) {
envFilePath = path.resolve(process.cwd(), arg)
} else {
command = arg
break
}
}
if (!envFilePath) {
throw new Error('Error! No -e or --env flag passed.')
}
return {
envFilePath,
command,
@ -50,38 +58,44 @@ function ParseArgs (args) {
}
}
function ParseEnvFile (envFilePath) {
let file = fs.readFileSync(envFilePath, { encoding: 'utf8' })
function ParseEnvString (envFileString) {
const envs = Object.assign({}, process.env)
while (file.length) {
// Get the full line
const line = file.slice(0, file.indexOf('\n') + 1)
while (envFileString.length) {
// The the last index of the line using the newline delimiter
let endOfLineIndex = envFileString.indexOf('\n')
// Shrink the file by 1 line
file = file.slice(line.length)
// Parse the line
const equalSign = line.indexOf('=')
if (equalSign === -1) {
throw new Error(`Error! Malformed line in ${path.parse(envFilePath).base}.`)
// If no newline, then assume end of file
if (endOfLineIndex === -1) {
endOfLineIndex = envFileString.length
}
// Set then new env var
envs[line.slice(0, equalSign)] = line.slice(line.indexOf('=') + 1, -1)
// 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)
}
}
return envs
}
function PrintHelp () {
return `
Usage: env-cmd -e [file] command [command options]
Usage: env-cmd env_file command [command options]
A simple application for running a cli application using an env config file
Options:
-e, --env Relative path to the env file
`
}
@ -98,7 +112,7 @@ process.on('uncaughtException', HandleUncaughtExceptions)
module.exports = {
EnvCmd,
ParseArgs,
ParseEnvFile,
ParseEnvString,
PrintHelp,
HandleUncaughtExceptions
}

View File

@ -1,6 +1,6 @@
{
"name": "env-cmd",
"version": "1.0.1",
"version": "2.0.0",
"description": "Executes a command using the envs in the provided env file",
"main": "lib/index.js",
"bin": {

View File

@ -1,3 +0,0 @@
BOB=COOL
NODE_ENV=dev
NICE=42

View File

@ -1,3 +0,0 @@
BOB=COOL
NODE_ENV dev
NICE=42

View File

@ -5,14 +5,18 @@ const describe = require('mocha').describe
const it = require('mocha').it
const afterEach = require('mocha').afterEach
const beforeEach = require('mocha').beforeEach
const before = require('mocha').before
const after = require('mocha').after
const path = require('path')
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const fs = require('fs')
const spawnStub = sinon.spy(() => ({
on: sinon.stub(),
exit: sinon.stub()
}))
const lib = proxyquire('../lib', {
'cross-spawn': {
spawn: spawnStub
@ -20,29 +24,29 @@ const lib = proxyquire('../lib', {
})
const EnvCmd = lib.EnvCmd
const ParseArgs = lib.ParseArgs
const ParseEnvFile = lib.ParseEnvFile
const ParseEnvString = lib.ParseEnvString
const PrintHelp = lib.PrintHelp
const HandleUncaughtExceptions = lib.HandleUncaughtExceptions
describe('env-cmd', function () {
describe('ParseArgs', function () {
it('should parse out the -e envfile path', function () {
const parsedArgs = ParseArgs(['-e', './test/envFile', 'command', 'cmda1', 'cmda2'])
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
assert(parsedArgs.envFilePath === path.join(__dirname, 'envFile'))
})
it('should parse out the --env envfile path', function () {
const parsedArgs = ParseArgs(['--env', './test/envFile', 'command', 'cmda1', 'cmda2'])
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
assert(parsedArgs.envFilePath === path.join(__dirname, 'envFile'))
})
it('should parse out the command', function () {
const parsedArgs = ParseArgs(['-e', './test/envFile', 'command', 'cmda1', 'cmda2'])
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
assert(parsedArgs.command === 'command')
})
it('should parse out the command args', function () {
const parsedArgs = ParseArgs(['-e', './test/envFile', 'command', 'cmda1', 'cmda2'])
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
assert(parsedArgs.commandArgs.length === 2)
assert(parsedArgs.commandArgs[0] === 'cmda1')
assert(parsedArgs.commandArgs[1] === 'cmda2')
@ -50,55 +54,80 @@ describe('env-cmd', function () {
it('should error out if incorrect number of args passed', function () {
try {
ParseArgs(['-e', './test/envFile'])
ParseArgs(['./test/envFile'])
} catch (e) {
assert(e.message === 'Error! Too few arguments passed to env-cmd.')
return
}
assert(!'No exepection thrown')
})
it('should error out if no envFile flag found', function () {
try {
ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
} catch (e) {
assert(e.message === 'Error! No -e or --env flag passed.')
return
}
assert(!'No exepection thrown')
assert(!'No exception thrown')
})
})
describe('ParseEnvFile', function () {
it('should parse out vars in the environment variable file', function () {
const env = ParseEnvFile(path.join(__dirname, '/.env'))
describe('ParseEnvString', function () {
it('should parse out vars in the environment variable string', function () {
const env = ParseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
assert(env.BOB === 'COOL')
assert(env.NODE_ENV === 'dev')
assert(env.NICE === '42')
assert(env.ANSWER === '42')
})
it('should parse out vars in the environment variable file', function () {
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 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 throw parse error due to malformed env var string', function () {
try {
ParseEnvFile(path.join(__dirname, '/.env-malformed'))
ParseEnvString('BOB=COOL\nNODE_ENV dev\nANSWER=42\n')
} catch (e) {
assert(e.message === 'Error! Malformed line in .env-malformed.')
assert(e.message === 'Error! Malformed line in env file.')
return
}
assert(!'No exepection thrown')
assert(!'No exception thrown')
})
})
describe('EnvCmd', function () {
before(function () {
this.readFileStub = sinon.stub(fs, 'readFileSync')
})
after(function () {
this.readFileStub.restore()
})
afterEach(function () {
spawnStub.reset()
this.readFileStub.reset()
})
it('should spawn a new process with the env vars set', function () {
EnvCmd(['-e', './test/.env', 'echo', '$BOB'])
this.readFileStub.returns('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
EnvCmd(['./test/.env', 'echo', '$BOB'])
assert(this.readFileStub.args[0][0] === path.join(process.cwd(), 'test/.env'))
assert(spawnStub.args[0][0] === 'echo')
assert(spawnStub.args[0][1][0] === '$BOB')
assert(spawnStub.args[0][2].env.BOB === 'COOL')
assert(spawnStub.args[0][2].env.NODE_ENV === 'dev')
assert(spawnStub.args[0][2].env.NICE === '42')
assert(spawnStub.args[0][2].env.ANSWER === '42')
})
it('should throw error if file does not exist', function () {
this.readFileStub.restore()
try {
EnvCmd(['./test/.non-existent-file', 'echo', '$BOB'])
} catch (e) {
const resolvedPath = path.join(process.cwd(), 'test/.non-existent-file')
assert(e.message === `Error! Could not find or read file at ${resolvedPath}`)
return
}
assert(!'No exception thrown')
})
})
@ -108,7 +137,7 @@ describe('env-cmd', function () {
assert(typeof helpText === 'string')
assert(helpText.match(/Usage/g).length !== 0)
assert(helpText.match(/env-cmd/).length !== 0)
assert(helpText.match(/-e/).length !== 0)
assert(helpText.match(/env_file/).length !== 0)
})
})
describe('HandleUncaughtExceptions', function () {