Added in support for .env-cmdrc file and updated to 3.0.0

This commit is contained in:
Todd Bluhm 2016-12-03 01:44:39 -06:00
parent 4bb8233bec
commit f37249e467
No known key found for this signature in database
GPG Key ID: 9CF312607477B8AB
6 changed files with 160 additions and 39 deletions

View File

@ -6,6 +6,7 @@ node_js:
- "4"
- "5"
- "6"
- "7"
env:
global:
@ -17,6 +18,7 @@ cache:
- node_modules
script:
- npm run lint
- npm run test-cover
after_script:

View File

@ -1,5 +1,10 @@
# Changelog
## 3.0.0
- **Feature**: Added ability to use an `.env-cmdrc` file to hold multiple configs
- **Feature**: Added ability to pass in a regular `.js` file exporting an object for your env file (special thanks to Jon Scheiding!)
- **Change**: Updated core `cross-spawn` lib to 5.0.1
## 2.2.0
- **Feature**: Added support for .json env files (special thanks to Eric Lanehart!)

View File

@ -3,6 +3,7 @@
[![npm](https://img.shields.io/npm/v/env-cmd.svg?maxAge=86400)](https://www.npmjs.com/package/env-cmd)
[![npm](https://img.shields.io/npm/dm/env-cmd.svg?maxAge=86400)](https://www.npmjs.com/package/env-cmd)
[![npm](https://img.shields.io/npm/l/env-cmd.svg?maxAge=2592000)](https://www.npmjs.com/package/env-cmd)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
# env-cmd
A simple node program for executing commands using an environment from an env file.
@ -33,6 +34,25 @@ or
```sh
./node_modules/.bin/env-cmd ./test/.env node index.js
```
or
**.env-cmdrc file `.env-cmdrc`**
```json
{
"development": {
"ENV1": "Thanks",
"ENV2": "For All"
},
"production": {
"ENV1": "The Fish"
}
}
```
```sh
./node_modules/.bin/env-cmd production node index.js
```
## Environment File Formats
@ -41,6 +61,7 @@ These are the currently accepted environment file formats. If any other formats
- `key value`
- Key/value pairs as JSON
- JavaScript file exporting an object
- `.env-cmdrc` file (as valid json) in execution directory
## Why
@ -60,3 +81,10 @@ Special thanks to [`cross-env`](https://github.com/kentcdodds/cross-env) for ins
- Eric Lanehart
- Jon Scheiding
## 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:
- `npm run lint` checks for code errors and formats according to [js-standard](https://github.com/feross/standard)
- `npm test` make sure all tests pass
- `npm run test-cover` make sure the coverage has not decreased from current master

View File

@ -3,52 +3,40 @@
const spawn = require('cross-spawn').spawn
const path = require('path')
const fs = require('fs')
const rcFileLocation = path.join(process.cwd(), '.env-cmdrc')
function EnvCmd (args) {
// Parse the args from the command line
// First Parse the args from the command line
const parsedArgs = ParseArgs(args)
// 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}`)
}
const ext = path.extname(parsedArgs.envFilePath).toLowerCase()
// Parse the env file string
const env = ext === '.json' || ext === '.js'
? Object.assign({}, process.env, require(parsedArgs.envFilePath))
: ParseEnvString(file)
// If a .rc file was found then use that
const env = fs.existsSync(rcFileLocation) ? UseRCFile(parsedArgs) : UseCmdLine(parsedArgs)
// Execute the command with the given environment variables
if (parsedArgs.command) {
const proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
stdio: 'inherit',
env
})
process.on('SIGTERM', () => proc.kill('SIGTERM'))
proc.on('exit', process.exit)
return proc
}
const proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
stdio: 'inherit',
env
})
process.on('SIGTERM', proc.kill.bind(proc, 'SIGTERM'))
proc.on('exit', process.exit)
return proc
}
// Parses the arguments passed into the cli
function ParseArgs (args) {
if (args.length < 2) {
throw new Error('Error! Too few arguments passed to env-cmd.')
}
let envFilePath
let envFile
let command
let commandArgs = args.slice()
while (commandArgs.length) {
const arg = commandArgs.shift()
// assume the first arg is the env file
if (!envFilePath) {
envFilePath = path.resolve(process.cwd(), arg)
// assume the first arg is the env file (or if using .rc the environment name)
if (!envFile) {
envFile = arg
} else {
command = arg
break
@ -56,22 +44,25 @@ function ParseArgs (args) {
}
return {
envFilePath,
envFile,
command,
commandArgs
}
}
// Strips out comments from env file string
function StripComments (envString) {
const commentsRegex = /[ ]*(#.*$)/gim
return envString.replace(commentsRegex, '')
}
// Strips out newlines from env file string
function StripEmptyLines (envString) {
const emptyLinesRegex = /(^\n)/gim
return envString.replace(emptyLinesRegex, '')
}
// Parse out all env vars from an env file string
function ParseEnvVars (envString) {
const envParseRegex = /^((.+?)[ =](.*))$/gim
const matches = {}
@ -83,6 +74,7 @@ function ParseEnvVars (envString) {
return matches
}
// Parse out all env vars from a given env file string and return an object
function ParseEnvString (envFileString) {
// First thing we do is stripe out all comments
envFileString = StripComments(envFileString)
@ -97,11 +89,49 @@ function ParseEnvString (envFileString) {
return Object.assign({}, process.env, envs)
}
// Reads and parses the .env-cmdrc file
function ParseRCFile (fileData) {
return JSON.parse(fileData)
}
// Uses the rc file to get env vars
function UseRCFile (parsedArgs) {
const fileData = fs.readFileSync(rcFileLocation, { encoding: 'utf8' })
const parsedData = ParseRCFile(fileData)
return parsedData[parsedArgs.envFile]
}
// Uses the cli passed env file to get env vars
function UseCmdLine (parsedArgs) {
const envFilePath = path.join(process.cwd(), parsedArgs.envFile)
// Attempt to open the provided file
let file
try {
file = fs.readFileSync(envFilePath, { encoding: 'utf8' })
} catch (e) {
throw new Error(`Error! Could not find or read file at ${envFilePath}`)
}
const ext = path.extname(envFilePath).toLowerCase()
// Parse the env file string using the correct parser
const env = ext === '.json' || ext === '.js'
? Object.assign({}, process.env, require(envFilePath))
: ParseEnvString(file)
return env
}
// Prints out some minor help text
function PrintHelp () {
return `
Usage: env-cmd env_file command [command options]
Usage: env-cmd [env_file | env_name] command [command options]
A simple application for running a cli application using an env config file
A simple utility for running a cli application using an env config file.
Also supports using a .env-cmdrc json file in the execution directory to support multiple
environment configs in one file.
`
}
@ -123,5 +153,8 @@ module.exports = {
HandleUncaughtExceptions,
StripComments,
StripEmptyLines,
ParseEnvVars
ParseEnvVars,
ParseRCFile,
UseRCFile,
UseCmdLine
}

View File

@ -1,6 +1,6 @@
{
"name": "env-cmd",
"version": "2.2.0",
"version": "3.0.0",
"description": "Executes a command using the envs in the provided env file",
"main": "lib/index.js",
"bin": {
@ -9,7 +9,9 @@
"scripts": {
"test": "mocha",
"test-cover": "istanbul cover node_modules/.bin/_mocha -- -R spec",
"coveralls": "coveralls < coverage/lcov.info"
"test-lint": "standard",
"coveralls": "coveralls < coverage/lcov.info",
"lint": "standard --fix"
},
"repository": {
"type": "git",
@ -42,6 +44,7 @@
"istanbul": "^0.4.4",
"mocha": "^3.0.2",
"proxyquire": "^1.7.10",
"sinon": "^1.17.5"
"sinon": "^1.17.5",
"standard": "^8.6.0"
}
}

View File

@ -14,7 +14,8 @@ const fs = require('fs')
const spawnStub = sinon.spy(() => ({
on: sinon.stub(),
exit: sinon.stub()
exit: sinon.stub(),
kill: sinon.stub()
}))
const lib = proxyquire('../lib', {
@ -43,9 +44,9 @@ const ParseEnvVars = lib.ParseEnvVars
describe('env-cmd', function () {
describe('ParseArgs', function () {
it('should parse out the envfile path', function () {
it('should parse out the envfile', function () {
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
assert(parsedArgs.envFilePath === path.join(__dirname, 'envFile'))
assert(parsedArgs.envFile === './test/envFile')
})
it('should parse out the command', function () {
@ -145,10 +146,12 @@ describe('env-cmd', function () {
proxyquire.noCallThru()
})
after(function () {
spawnStub.reset()
this.readFileStub.restore()
proxyquire.callThru()
})
afterEach(function () {
spawnStub.reset()
})
it('should parse env vars from JSON with node module loader if file extension is .json', function () {
EnvCmd(['./test/.env.json', 'echo', '$BOB'])
assert(spawnStub.args[0][0] === 'echo')
@ -167,6 +170,51 @@ describe('env-cmd', function () {
})
})
describe('.RC file support (.env-cmdrc)', function () {
before(function () {
this.readFileStub = sinon.stub(fs, 'readFileSync')
this.readFileStub.returns(`{
"development": {
"BOB": "COOL",
"NODE_ENV": "dev",
"ANSWER": "42"
},
"production": {
"BOB": "COOL",
"NODE_ENV": "prod",
"ANSWER": "43"
}
}`)
this.existsSyncStub = sinon.stub(fs, 'existsSync')
this.existsSyncStub.returns(true)
proxyquire.noCallThru()
})
after(function () {
this.readFileStub.restore()
this.existsSyncStub.restore()
proxyquire.callThru()
})
afterEach(function () {
spawnStub.reset()
})
it('should parse env vars from .env-cmdrc file using development env', function () {
EnvCmd(['development', 'echo', '$BOB'])
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.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')
assert(spawnStub.args[0][1][0] === '$BOB')
assert(spawnStub.args[0][2].env.BOB === 'COOL')
assert(spawnStub.args[0][2].env.NODE_ENV === 'prod')
assert(spawnStub.args[0][2].env.ANSWER === '43')
})
})
describe('EnvCmd', function () {
before(function () {
this.readFileStub = sinon.stub(fs, 'readFileSync')
@ -209,8 +257,10 @@ describe('env-cmd', function () {
assert(helpText.match(/Usage/g).length !== 0)
assert(helpText.match(/env-cmd/).length !== 0)
assert(helpText.match(/env_file/).length !== 0)
assert(helpText.match(/env_name/).length !== 0)
})
})
describe('HandleUncaughtExceptions', function () {
beforeEach(function () {
this.logStub = sinon.stub(console, 'log')