You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

env-cmd

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

env-cmd - npm Package Compare versions

Comparing version
8.0.2
to
9.0.0
.DS_Store

Sorry, the diff of this file is not supported yet

+21
import { EnvCmdOptions } from './types';
/**
* Executes env - cmd using command line arguments
* @export
* @param {string[]} args Command line argument to pass in ['-f', './.env']
* @returns {Promise<{ [key: string]: any }>}
*/
export declare function CLI(args: string[]): Promise<{
[key: string]: any;
}>;
/**
* The main env-cmd program. This will spawn a new process and run the given command using
* various environment file solutions.
*
* @export
* @param {EnvCmdOptions} { command, commandArgs, envFile, rc, options }
* @returns {Promise<{ [key: string]: any }>} Returns an object containing [environment variable name]: value
*/
export declare function EnvCmd({ command, commandArgs, envFile, rc, options }: EnvCmdOptions): Promise<{
[key: string]: any;
}>;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const spawn_1 = require("./spawn");
const signal_termination_1 = require("./signal-termination");
const parse_args_1 = require("./parse-args");
const get_env_vars_1 = require("./get-env-vars");
/**
* Executes env - cmd using command line arguments
* @export
* @param {string[]} args Command line argument to pass in ['-f', './.env']
* @returns {Promise<{ [key: string]: any }>}
*/
function CLI(args) {
return __awaiter(this, void 0, void 0, function* () {
// Parse the args from the command line
const parsedArgs = parse_args_1.parseArgs(args);
// Run EnvCmd
return exports.EnvCmd(parsedArgs);
});
}
exports.CLI = CLI;
/**
* The main env-cmd program. This will spawn a new process and run the given command using
* various environment file solutions.
*
* @export
* @param {EnvCmdOptions} { command, commandArgs, envFile, rc, options }
* @returns {Promise<{ [key: string]: any }>} Returns an object containing [environment variable name]: value
*/
function EnvCmd({ command, commandArgs, envFile, rc, options }) {
return __awaiter(this, void 0, void 0, function* () {
options = options || {};
let env = yield get_env_vars_1.getEnvVars({ envFile, rc });
// Override the merge order if --no-override flag set
if (options.noOverride) {
env = Object.assign({}, env, process.env);
}
else {
// Add in the system environment variables to our environment list
env = Object.assign({}, process.env, env);
}
// Execute the command with the given environment variables
const proc = spawn_1.spawn(command, commandArgs, {
stdio: 'inherit',
shell: options.useShell,
env
});
// Handle any termination signals for parent and child proceses
const signals = new signal_termination_1.TermSignals();
signals.handleUncaughtExceptions();
signals.handleTermSignals(proc);
return env;
});
}
exports.EnvCmd = EnvCmd;
import { GetEnvVarOptions } from './types';
export declare function getEnvVars(options?: GetEnvVarOptions): Promise<{
[key: string]: any;
}>;
export declare function getEnvFile({ filePath, fallback }: {
filePath?: string;
fallback?: boolean;
}): Promise<{
[key: string]: any;
}>;
export declare function getRCFile({ environments, filePath }: {
environments: string[];
filePath?: string;
}): Promise<{
[key: string]: any;
}>;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const parse_rc_file_1 = require("./parse-rc-file");
const parse_env_file_1 = require("./parse-env-file");
const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.js', './.env-cmdrc.json'];
const ENV_FILE_DEFAULT_LOCATION = './.env';
function getEnvVars(options) {
return __awaiter(this, void 0, void 0, function* () {
options = options || {};
options.envFile = options.envFile || {};
// Check for rc file usage
if (options.rc) {
return getRCFile({ environments: options.rc.environments, filePath: options.rc.filePath });
}
return getEnvFile({ filePath: options.envFile.filePath, fallback: options.envFile.fallback });
});
}
exports.getEnvVars = getEnvVars;
function getEnvFile({ filePath, fallback }) {
return __awaiter(this, void 0, void 0, function* () {
// Use env file
if (filePath) {
try {
return yield parse_env_file_1.getEnvFileVars(filePath);
}
catch (e) { }
if (!fallback) {
throw new Error(`Unable to locate env file at location (${filePath})`);
}
}
// Use the default env file location
try {
return yield parse_env_file_1.getEnvFileVars(ENV_FILE_DEFAULT_LOCATION);
}
catch (e) {
throw new Error(`Unable to locate env file at default location (${ENV_FILE_DEFAULT_LOCATION})`);
}
});
}
exports.getEnvFile = getEnvFile;
function getRCFile({ environments, filePath }) {
return __awaiter(this, void 0, void 0, function* () {
// User provided an .rc file path
if (filePath) {
try {
return parse_rc_file_1.getRCFileVars({ environments, filePath });
}
catch (e) {
throw new Error(`Unable to locate .rc file at location (${filePath})`);
}
}
// Use the default .rc file locations
for (const path of RC_FILE_DEFAULT_LOCATIONS) {
try {
return parse_rc_file_1.getRCFileVars({ environments, filePath: path });
}
catch (e) { }
}
throw new Error(`Unable to locate .rc file at default locations (${RC_FILE_DEFAULT_LOCATIONS})`);
});
}
exports.getRCFile = getRCFile;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Prints out some minor help text
* @return {String} Help text
*/
function PrintHelp() {
return "\nUsage: env-cmd [options] [env_file | env_name] command [command options]\n\nA simple utility for running a cli application using an env config file.\n\nAlso supports using a .env-cmdrc json file in the execution directory to support multiple\nenvironment configs in one file.\n\nOptions:\n --no-override - do not override existing process env vars with file env vars\n --fallback - if provided env file does not exist, attempt to use fallback .env file in root dir\n ";
}
exports.PrintHelp = PrintHelp;
import { getEnvVars } from './get-env-vars';
export * from './types';
export * from './env-cmd';
export declare const GetEnvVars: typeof getEnvVars;
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
const get_env_vars_1 = require("./get-env-vars");
__export(require("./env-cmd"));
exports.GetEnvVars = get_env_vars_1.getEnvVars;
import { EnvCmdOptions } from './types';
/**
* Parses the arguments passed into the cli
*/
export declare function parseArgs(args: string[]): EnvCmdOptions;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const commander_1 = require("commander");
const utils_1 = require("./utils");
/**
* Parses the arguments passed into the cli
*/
function parseArgs(args) {
const program = new commander_1.Command();
program
.version('9.0.0', '-v, --version')
.usage('[options] <command> [...args]')
.option('-f, --file [path]', 'Custom env file file path (default path: ./.env)')
.option('-r, --rc-file [path]', 'Custom rc file path (default path: ./.env-cmdrc(|.js|.json)')
.option('-e, --environments [env1,env2,...]', 'The rc file environment(s) to use', utils_1.parseArgList)
.option('--fallback', 'Fallback to default env file path, if custom env file path not found')
.option('--no-override', 'Do not override existing environment variables')
.option('--use-shell', 'Execute the command in a new shell with the given environment')
.parse(['_', '_', ...args]);
// get the command and command args
const command = program.args[0];
const commandArgs = program.args.slice(1);
const noOverride = !program.override;
const useShell = !!program.useShell;
let rc;
if (program.environments && program.environments.length) {
rc = rc || {};
rc.environments = program.environments;
rc.filePath = program.rcFile;
}
let envFile;
if (program.file) {
envFile = envFile || {};
envFile.filePath = program.file;
envFile.fallback = program.fallback;
}
return {
command,
commandArgs,
envFile,
rc,
options: {
noOverride,
useShell
}
};
}
exports.parseArgs = parseArgs;
/**
* Gets the environment vars from an env file
*/
export declare function getEnvFileVars(envFilePath: string): Promise<{
[key: string]: any;
}>;
/**
* Parse out all env vars from a given env file string and return an object
*/
export declare function parseEnvString(envFileString: string): {
[key: string]: string;
};
/**
* Parse out all env vars from an env file string
*/
export declare function parseEnvVars(envString: string): {
[key: string]: string;
};
/**
* Strips out comments from env file string
*/
export declare function stripComments(envString: string): string;
/**
* Strips out newlines from env file string
*/
export declare function stripEmptyLines(envString: string): string;
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const utils_1 = require("./utils");
const REQUIRE_HOOK_EXTENSIONS = ['.json', '.js'];
/**
* Gets the environment vars from an env file
*/
function getEnvFileVars(envFilePath) {
return __awaiter(this, void 0, void 0, function* () {
const absolutePath = utils_1.resolveEnvFilePath(envFilePath);
if (!fs.existsSync(absolutePath)) {
throw new Error(`Invalid env file path (${envFilePath}).`);
}
// Get the file extension
const ext = path.extname(absolutePath).toLowerCase();
let env = {};
if (~REQUIRE_HOOK_EXTENSIONS.indexOf(ext)) {
const possiblePromise = require(absolutePath); /* eslint-disable-line */
env = utils_1.isPromise(possiblePromise) ? yield possiblePromise : possiblePromise;
}
else {
const file = fs.readFileSync(absolutePath, { encoding: 'utf8' });
env = parseEnvString(file);
}
return env;
});
}
exports.getEnvFileVars = getEnvFileVars;
/**
* 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.toString());
// Next we stripe out all the empty lines
envFileString = stripEmptyLines(envFileString);
// Merge the file env vars with the current process env vars (the file vars overwrite process vars)
return parseEnvVars(envFileString);
}
exports.parseEnvString = parseEnvString;
/**
* Parse out all env vars from an env file string
*/
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
const key = match[2].trim();
const value = match[3].trim() || '';
// remove any surrounding quotes
matches[key] = value
.replace(/(^['"]|['"]$)/g, '')
.replace(`\\n`, `\n`);
}
return matches;
}
exports.parseEnvVars = parseEnvVars;
/**
* Strips out comments from env file string
*/
function stripComments(envString) {
const commentsRegex = /(^#.*$)/gim;
let match = commentsRegex.exec(envString);
let newString = envString;
while (match != null) {
newString = newString.replace(match[1], '');
match = commentsRegex.exec(envString);
}
return newString;
}
exports.stripComments = stripComments;
/**
* Strips out newlines from env file string
*/
function stripEmptyLines(envString) {
const emptyLinesRegex = /(^\n)/gim;
return envString.replace(emptyLinesRegex, '');
}
exports.stripEmptyLines = stripEmptyLines;
/**
* Gets the env vars from the rc file and rc environments
*/
export declare function getRCFileVars({ environments, filePath }: {
environments: string[];
filePath: string;
}): Promise<{
[key: string]: any;
}>;
/**
* Reads and parses the .rc file
*/
export declare function parseRCFile(fileData: string): {
[key: string]: any;
};
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const util_1 = require("util");
const path_1 = require("path");
const utils_1 = require("./utils");
const statAsync = util_1.promisify(fs_1.stat);
const readFileAsync = util_1.promisify(fs_1.readFile);
/**
* Gets the env vars from the rc file and rc environments
*/
function getRCFileVars({ environments, filePath }) {
return __awaiter(this, void 0, void 0, function* () {
const absolutePath = utils_1.resolveEnvFilePath(filePath);
try {
yield statAsync(absolutePath);
}
catch (e) {
throw new Error('Invalid .rc file path.');
}
// Get the file extension
const ext = path_1.extname(absolutePath).toLowerCase();
let parsedData;
if (ext === '.json' || ext === '.js') {
const possiblePromise = require(absolutePath); /* eslint-disable-line */
parsedData = utils_1.isPromise(possiblePromise) ? yield possiblePromise : possiblePromise;
}
else {
const file = yield readFileAsync(absolutePath, { encoding: 'utf8' });
parsedData = parseRCFile(file);
}
// Parse and merge multiple rc environments together
let result = {};
let environmentFound = false;
environments.forEach((name) => {
const envVars = parsedData[name];
if (envVars) {
environmentFound = true;
result = Object.assign({}, result, envVars);
}
});
if (!environmentFound) {
console.error(`Error:
Could not find any environments:
${environments}
in .rc file:
${absolutePath}`);
throw new Error(`All environments (${environments}) are missing in in .rc file (${absolutePath}).`);
}
return result;
});
}
exports.getRCFileVars = getRCFileVars;
/**
* Reads and parses the .rc file
*/
function parseRCFile(fileData) {
let data;
try {
data = JSON.parse(fileData);
}
catch (e) {
console.error(`Error:
Failed to parse the .rc file.
Please make sure its a valid JSON format.`);
throw new Error(`Unable to parse JSON in .rc file.`);
}
return data;
}
exports.parseRCFile = parseRCFile;
/// <reference types="node" />
import { ChildProcess } from 'child_process';
export declare class TermSignals {
private terminateSpawnedProcessFuncHandlers;
_exitCalled: boolean;
handleTermSignals(proc: ChildProcess): void;
/**
* Enables catching of unhandled exceptions
*/
handleUncaughtExceptions(): void;
/**
* Terminate parent process helper
*/
_terminateProcess(code?: number, signal?: string): void;
/**
* Exit event listener clean up helper
*/
_removeProcessListeners(): void;
/**
* General exception handler
*/
_uncaughtExceptionHandler(e: Error): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const SIGNALS_TO_HANDLE = [
'SIGINT', 'SIGTERM', 'SIGHUP'
];
class TermSignals {
constructor() {
this.terminateSpawnedProcessFuncHandlers = {};
this._exitCalled = false;
}
handleTermSignals(proc) {
// Terminate child process if parent process receives termination events
SIGNALS_TO_HANDLE.forEach((signal) => {
this.terminateSpawnedProcessFuncHandlers[signal] =
(signal, code) => {
this._removeProcessListeners();
if (!this._exitCalled) {
this._exitCalled = true;
proc.kill(signal);
this._terminateProcess(code, signal);
}
};
process.once(signal, this.terminateSpawnedProcessFuncHandlers[signal]);
});
process.once('exit', this.terminateSpawnedProcessFuncHandlers['SIGTERM']);
// Terminate parent process if child process receives termination events
proc.on('exit', (code, signal) => {
this._removeProcessListeners();
if (!this._exitCalled) {
this._exitCalled = true;
this._terminateProcess(code, signal);
}
});
}
/**
* Enables catching of unhandled exceptions
*/
handleUncaughtExceptions() {
process.on('uncaughtException', (e) => this._uncaughtExceptionHandler(e));
}
/**
* Terminate parent process helper
*/
_terminateProcess(code, signal) {
if (signal != null) {
return process.kill(process.pid, signal);
}
if (code != null) {
return process.exit(code);
}
throw new Error('Unable to terminate parent process successfully');
}
/**
* Exit event listener clean up helper
*/
_removeProcessListeners() {
SIGNALS_TO_HANDLE.forEach((signal) => {
process.removeListener(signal, this.terminateSpawnedProcessFuncHandlers[signal]);
});
process.removeListener('exit', this.terminateSpawnedProcessFuncHandlers['SIGTERM']);
}
/**
* General exception handler
*/
_uncaughtExceptionHandler(e) {
console.error(e.message);
process.exit(1);
}
}
exports.TermSignals = TermSignals;
import * as spawn from 'cross-spawn';
export { spawn };
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const spawn = require("cross-spawn");
exports.spawn = spawn;
export interface GetEnvVarOptions {
envFile?: {
filePath?: string;
fallback?: boolean;
};
rc?: {
environments: string[];
filePath?: string;
};
}
export interface EnvCmdOptions extends GetEnvVarOptions {
command: string;
commandArgs: string[];
options?: {
noOverride?: boolean;
useShell?: boolean;
};
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* A simple function for resolving the path the user entered
*/
export declare function resolveEnvFilePath(userPath: string): string;
/**
* A simple function that parses a comma separated string into an array of strings
*/
export declare function parseArgList(list: string): string[];
/**
* A simple function to test if the value is a promise
*/
export declare function isPromise(value: any | PromiseLike<Object>): boolean;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const os = require("os");
/**
* A simple function for resolving the path the user entered
*/
function resolveEnvFilePath(userPath) {
// Make sure a home directory exist
const home = os.homedir();
if (home) {
userPath = userPath.replace(/^~($|\/|\\)/, `${home}$1`);
}
return path.resolve(process.cwd(), userPath);
}
exports.resolveEnvFilePath = resolveEnvFilePath;
/**
* A simple function that parses a comma separated string into an array of strings
*/
function parseArgList(list) {
return list.split(',');
}
exports.parseArgList = parseArgList;
/**
* A simple function to test if the value is a promise
*/
function isPromise(value) {
return value && typeof value.then === 'function';
}
exports.isPromise = isPromise;
+1
-1
#! /usr/bin/env node
require('../lib').EnvCmd(process.argv.slice(2))
require('../dist').CLI(process.argv.slice(2))
# Changelog
## 9.0.0
- ***BREAKING***: Converted project to Typescript
- ***BREAKING***: Changes to all option flags, see docs for new options
- ***BREAKING***: Dropping support for node v4 and v6
- **Change**: Updated all dependencies
- **Change**: Update package-lock.json file
- **Feature**: Added support for asynchronous .env and .rc files
- **Feature**: Added support for a programmatic API
- **Feature**: Added --use-shell option (thanks to nidkil)
- **Fix**: Keep newline (`\n`) characters intact when parsing env files
- **Change**: Added node v10 and v12 to build automation
- **Change**: Updated Readme file to reflect new options and CLI changes
## 8.0.2

@@ -3,0 +16,0 @@ - **Change**: Updated dependencies and packages.json to fix `npm audit` concerns.

MIT License
Copyright (c) 2017 Todd Bluhm
Copyright (c) 2019 Todd Bluhm

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "env-cmd",
"version": "8.0.2",
"version": "9.0.0",
"description": "Executes a command using the envs in the provided env file",
"main": "lib/index.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"engines": {
"node": ">=4.0.0"
"node": ">=8.0.0"
},

@@ -13,7 +14,8 @@ "bin": {

"scripts": {
"test": "mocha",
"test": "mocha -r ts-node/register ./test/**/*.ts",
"test-cover": "nyc --reporter=lcov --reporter=text npm test",
"test-lint": "standard",
"test-lint": "eslint ./src/**/*.ts ./test/**/*.ts",
"coveralls": "coveralls < coverage/lcov.info",
"lint": "standard --fix"
"lint": "eslint --fix ./src/**/*.ts ./test/**/*.ts",
"build": "tsc"
},

@@ -45,13 +47,44 @@ "repository": {

"dependencies": {
"cross-spawn": "^6.0.5"
"commander": "^2.20.0",
"cross-spawn": "6.0.5"
},
"devDependencies": {
"better-assert": "^1.0.2",
"coveralls": "^3.0.1",
"mocha": "^5.1.1",
"nyc": "^11.8.0",
"proxyquire": "^2.0.1",
"sinon": "^5.0.7",
"standard": "^11.0.1"
"@types/chai": "4.1.7",
"@types/cross-spawn": "6.0.0",
"@types/mocha": "5.2.6",
"@types/node": "^12.0.0",
"@types/sinon": "^7.0.11",
"@typescript-eslint/eslint-plugin": "^1.7.0",
"@typescript-eslint/parser": "^1.7.0",
"chai": "4.2.0",
"coveralls": "3.0.3",
"eslint": "^5.16.0",
"eslint-config-standard-with-typescript": "^7.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^9.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-standard": "^4.0.0",
"mocha": "^6.1.4",
"nyc": "^14.1.0",
"sinon": "^7.3.2",
"ts-node": "^8.1.0",
"typescript": "^3.4.5"
},
"nyc": {
"include": [
"src/**/*.ts"
],
"extension": [
".ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary",
"html"
],
"sourceMap": true,
"instrument": true
}
}
+94
-52

@@ -16,3 +16,3 @@ [![Travis](https://img.shields.io/travis/toddbluhm/env-cmd.svg)](https://travis-ci.org/toddbluhm/env-cmd)

**Environment file `./test/.env`**
**Environment file `./.env`**
```

@@ -29,56 +29,37 @@ # This is a comment

"scripts": {
"test": "env-cmd ./test/.env mocha -R spec"
"test": "env-cmd mocha -R spec"
}
}
```
or
**Terminal**
```sh
./node_modules/.bin/env-cmd ./test/.env node index.js
./node_modules/.bin/env-cmd node index.js
```
## Advanced Usage
### `--fallback` file usage option
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`**
## 📜 Help
```
# This is a comment
ENV1=THANKS
ENV2=FOR ALL
ENV3=THE FISH
```
**Fallback Environment file `./.env`**
```
# This can be used as an example fallback
ENV1=foo
ENV2=bar
ENV3=baz
ENV4=quux
ENV5=gorge
```
Usage: _ [options] <command> [...args]
**Package.json**
uses `./.env` as a fallback
```json
{
"scripts": {
"test": "env-cmd --fallback ./.env.local mocha -R spec"
}
}
Options:
-v, --version output the version number
-f, --file [path] Custom env file file path (default path: ./.env)
-r, --rc-file [path] Custom rc file path (default path: ./.env-cmdrc(|.js|.json)
-e, --environments [env1,env2,...] The rc file environment(s) to use
--fallback Fallback to default env file path, if custom env file path not found
--no-override Do not override existing environment variables
--use-shell Execute the command in a new shell with the given environment
-h, --help output usage information
```
or
**Terminal**
```sh
# uses ./.env as a fallback, because it can't find `./.env.local`
./node_modules/.bin/env-cmd ./.env.local node index.js
```
## 🔬 Advanced Usage
### `.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`, simply pass the name of the environment you want to use thats in your `.env-cmdrc` file. You may also use multiple environment names to merge env vars together.
For more complex projects, a `.env-cmdrc` file can be defined in the root directory and supports
as many environments as you want. Simply use the `-e` flag and provide which environments you wish to
use from the `.env-cmdrc` file. Using multiple environment names will merge the environment variables
together. Later environments overwrite earlier ones in the list if conflicting environment variables
are found.

@@ -105,6 +86,6 @@ **.rc file `.env-cmdrc`**

```sh
./node_modules/.bin/env-cmd production node index.js
./node_modules/.bin/env-cmd -e production node index.js
# Or for multiple environments (where `production` vars override `test` vars,
# but both are included)
./node_modules/.bin/env-cmd test,production node index.js
./node_modules/.bin/env-cmd -e test,production node index.js
```

@@ -114,8 +95,37 @@

Sometimes you want to set env variables from a file without overriding existing process env vars or shell env vars.
Prevents overriding of existing environment variables on `process.env` and within the current
environment.
### `--fallback` file usage option
If the `.env` file does not exist at the provieded custom path, then use the default
fallback location `./.env` env file instead.
### `--use-shell`
Executes the command within a new shell environment. This is useful if you want to string multiple
commands together that share the same environment variables.
**Terminal**
```sh
ENV1=welcome ./node_modules/.bin/env-cmd --no-override ./test/.env node index.js
./node_modules/.bin/env-cmd -f ./test/.env --use-shell "node run lint && node test"
```
### Asynchronous env file support
EnvCmd supports reading from asynchronous `.env` files. Instead of using a `.env` file, pass in a `.js`
file that returns a `Promise` resolving to an object (`{ ENV_VAR_NAME: value, ... }`). Asynchronous `.rc`
files are also supported using `.js` file extension and resolving to an object with top level environment
names (`{ production: { ENV_VAR_NAME: value, ... } }`).
**Terminal**
```sh
./node_modules/.bin/env-cmd -f ./async-file.js node index.js
```
## Examples
You can find examples of how to use the various options above by visiting
the examples repo [env-cmd-examples](https://github.com/toddbluhm/env-cmd-examples).
## Environment File Formats

@@ -126,3 +136,3 @@

- Key/value pairs as JSON
- JavaScript file exporting an object
- JavaScript file exporting an `object` or a `Promise` that resolves to an `object`
- `.env-cmdrc` file (as valid json) in execution directory

@@ -145,7 +155,37 @@

## ⚒ API Usage
### `EnvCmd`
A function that executes a given command in a new child process with the given environment and options
- **`options`** { `object` }
- **`command`** { `string` }: The command to execute (`node`, `mocha`, ...)
- **`commandArgs`** { `string[]` }: List of arguments to pass to the `command` (`['-R', 'Spec']`)
- **`envFile`** { `object` }
- **`filePath`** { `string` }: Custom path to .env file to read from (defaults to: `./.env`)
- **`fallback`** { `boolean` }: Should fall back to default `./.env` file if custom path does not exist
- **`rc`** { `object` }
- **`environments`** { `string[]` }: List of environment to read from the `.rc` file
- **`filePath`** { `string` }: Custom path to the `.rc` file (defaults to: `./.env-cmdrc(|.js|.json)`)
- **`options`** { `object` }
- **`noOverride`** { `boolean` }: Prevent `.env` file vars from overriding existing `process.env` vars (default: `false`)
- **`useShell`** { `boolean` }: Runs command inside a new shell instance (default: `false`)
- **Returns** { `Promise<object>` }: key is env var name and value is the env var value
### `GetEnvVars`
A function that parses environment variables from a `.env` or a `.rc` file
- **`options`** { `object` }
- **`envFile`** { `object` }
- **`filePath`** { `string` }: Custom path to .env file to read from (defaults to: `./.env`)
- **`fallback`** { `boolean` }: Should fall back to default `./.env` file if custom path does not exist
- **`rc`** { `object` }
- **`environments`** { `string[]` }: List of environment to read from the `.rc` file
- **`filePath`** { `string` }: Custom path to the `.rc` file (defaults to: `./.env-cmdrc(|.js|.json)`)
- **Returns** { `Promise<object>` }: key is env var name and value is the env var 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.
Because sometimes it is just too cumbersome passing a lot of environment variables to scripts. It is
usually just easier to have a file with all the vars in them, especially for development and testing.
**Do not commit sensitive environment data to a public git repo!**
🚨**Do not commit sensitive environment data to a public git repo!** 🚨

@@ -156,7 +196,8 @@ ## Related Projects

## Special Thanks
## 🎊 Special Thanks
Special thanks to [`cross-env`](https://github.com/kentcdodds/cross-env) for inspiration (use's the same `cross-spawn` lib underneath too).
Special thanks to [`cross-env`](https://github.com/kentcdodds/cross-env) for inspiration (use's the
same `cross-spawn` lib underneath too).
## Contributors
## 🎉 Contributors

@@ -168,7 +209,8 @@ - Eric Lanehart

## 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:
## 📋 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 run lint` checks for code errors and format 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
{}
{"/Users/toddbluhm/Development/Github/env-cmd/lib/index.js":{"path":"/Users/toddbluhm/Development/Github/env-cmd/lib/index.js","statementMap":{"0":{"start":{"line":3,"column":14},"end":{"line":3,"column":42}},"1":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"2":{"start":{"line":5,"column":11},"end":{"line":5,"column":24}},"3":{"start":{"line":6,"column":11},"end":{"line":6,"column":24}},"4":{"start":{"line":7,"column":23},"end":{"line":7,"column":61}},"5":{"start":{"line":8,"column":27},"end":{"line":8,"column":59}},"6":{"start":{"line":9,"column":44},"end":{"line":9,"column":46}},"7":{"start":{"line":11,"column":26},"end":{"line":13,"column":1}},"8":{"start":{"line":14,"column":20},"end":{"line":16,"column":1}},"9":{"start":{"line":26,"column":21},"end":{"line":26,"column":36}},"10":{"start":{"line":30,"column":2},"end":{"line":35,"column":3}},"11":{"start":{"line":31,"column":4},"end":{"line":31,"column":58}},"12":{"start":{"line":34,"column":4},"end":{"line":34,"column":96}},"13":{"start":{"line":39,"column":2},"end":{"line":44,"column":3}},"14":{"start":{"line":40,"column":4},"end":{"line":40,"column":51}},"15":{"start":{"line":43,"column":4},"end":{"line":43,"column":51}},"16":{"start":{"line":47,"column":15},"end":{"line":50,"column":4}},"17":{"start":{"line":54,"column":2},"end":{"line":57,"column":4}},"18":{"start":{"line":55,"column":4},"end":{"line":55,"column":102}},"19":{"start":{"line":56,"column":4},"end":{"line":56,"column":69}},"20":{"start":{"line":58,"column":2},"end":{"line":58,"column":70}},"21":{"start":{"line":60,"column":2},"end":{"line":60,"column":72}},"22":{"start":{"line":61,"column":2},"end":{"line":61,"column":46}},"23":{"start":{"line":63,"column":2},"end":{"line":63,"column":13}},"24":{"start":{"line":73,"column":2},"end":{"line":75,"column":3}},"25":{"start":{"line":74,"column":4},"end":{"line":74,"column":66}},"26":{"start":{"line":81,"column":20},"end":{"line":81,"column":32}},"27":{"start":{"line":82,"column":2},"end":{"line":99,"column":3}},"28":{"start":{"line":83,"column":16},"end":{"line":83,"column":35}},"29":{"start":{"line":84,"column":4},"end":{"line":87,"column":5}},"30":{"start":{"line":85,"column":6},"end":{"line":85,"column":24}},"31":{"start":{"line":86,"column":6},"end":{"line":86,"column":14}},"32":{"start":{"line":88,"column":4},"end":{"line":91,"column":5}},"33":{"start":{"line":89,"column":6},"end":{"line":89,"column":23}},"34":{"start":{"line":90,"column":6},"end":{"line":90,"column":14}},"35":{"start":{"line":93,"column":4},"end":{"line":98,"column":5}},"36":{"start":{"line":94,"column":6},"end":{"line":94,"column":19}},"37":{"start":{"line":96,"column":6},"end":{"line":96,"column":19}},"38":{"start":{"line":97,"column":6},"end":{"line":97,"column":11}},"39":{"start":{"line":101,"column":2},"end":{"line":107,"column":3}},"40":{"start":{"line":117,"column":24},"end":{"line":117,"column":36}},"41":{"start":{"line":118,"column":14},"end":{"line":118,"column":43}},"42":{"start":{"line":119,"column":18},"end":{"line":119,"column":27}},"43":{"start":{"line":120,"column":2},"end":{"line":123,"column":3}},"44":{"start":{"line":121,"column":4},"end":{"line":121,"column":47}},"45":{"start":{"line":122,"column":4},"end":{"line":122,"column":41}},"46":{"start":{"line":124,"column":2},"end":{"line":124,"column":18}},"47":{"start":{"line":134,"column":26},"end":{"line":134,"column":36}},"48":{"start":{"line":135,"column":2},"end":{"line":135,"column":47}},"49":{"start":{"line":145,"column":24},"end":{"line":145,"column":45}},"50":{"start":{"line":146,"column":18},"end":{"line":146,"column":20}},"51":{"start":{"line":148,"column":2},"end":{"line":157,"column":3}},"52":{"start":{"line":150,"column":16},"end":{"line":150,"column":31}},"53":{"start":{"line":151,"column":16},"end":{"line":151,"column":37}},"54":{"start":{"line":154,"column":4},"end":{"line":154,"column":47}},"55":{"start":{"line":156,"column":4},"end":{"line":156,"column":24}},"56":{"start":{"line":158,"column":2},"end":{"line":158,"column":16}},"57":{"start":{"line":169,"column":2},"end":{"line":169,"column":57}},"58":{"start":{"line":172,"column":2},"end":{"line":172,"column":48}},"59":{"start":{"line":175,"column":2},"end":{"line":175,"column":36}},"60":{"start":{"line":186,"column":2},"end":{"line":193,"column":3}},"61":{"start":{"line":187,"column":4},"end":{"line":187,"column":31}},"62":{"start":{"line":189,"column":4},"end":{"line":191,"column":48}},"63":{"start":{"line":192,"column":4},"end":{"line":192,"column":63}},"64":{"start":{"line":194,"column":2},"end":{"line":194,"column":13}},"65":{"start":{"line":205,"column":19},"end":{"line":205,"column":72}},"66":{"start":{"line":206,"column":21},"end":{"line":206,"column":42}},"67":{"start":{"line":208,"column":15},"end":{"line":208,"column":17}},"68":{"start":{"line":209,"column":19},"end":{"line":209,"column":45}},"69":{"start":{"line":210,"column":2},"end":{"line":217,"column":3}},"70":{"start":{"line":211,"column":4},"end":{"line":215,"column":23}},"71":{"start":{"line":216,"column":4},"end":{"line":216,"column":81}},"72":{"start":{"line":219,"column":2},"end":{"line":224,"column":4}},"73":{"start":{"line":220,"column":20},"end":{"line":220,"column":36}},"74":{"start":{"line":221,"column":4},"end":{"line":223,"column":5}},"75":{"start":{"line":222,"column":6},"end":{"line":222,"column":45}},"76":{"start":{"line":225,"column":2},"end":{"line":225,"column":15}},"77":{"start":{"line":237,"column":22},"end":{"line":237,"column":57}},"78":{"start":{"line":241,"column":2},"end":{"line":247,"column":3}},"79":{"start":{"line":242,"column":4},"end":{"line":242,"column":61}},"80":{"start":{"line":244,"column":4},"end":{"line":246,"column":5}},"81":{"start":{"line":245,"column":6},"end":{"line":245,"column":15}},"82":{"start":{"line":250,"column":2},"end":{"line":256,"column":3}},"83":{"start":{"line":251,"column":4},"end":{"line":255,"column":5}},"84":{"start":{"line":252,"column":6},"end":{"line":252,"column":48}},"85":{"start":{"line":254,"column":6},"end":{"line":254,"column":102}},"86":{"start":{"line":259,"column":14},"end":{"line":259,"column":53}},"87":{"start":{"line":262,"column":14},"end":{"line":264,"column":26}},"88":{"start":{"line":266,"column":2},"end":{"line":266,"column":12}},"89":{"start":{"line":274,"column":2},"end":{"line":285,"column":3}},"90":{"start":{"line":293,"column":2},"end":{"line":295,"column":3}},"91":{"start":{"line":294,"column":4},"end":{"line":294,"column":28}},"92":{"start":{"line":296,"column":2},"end":{"line":296,"column":24}},"93":{"start":{"line":297,"column":2},"end":{"line":297,"column":17}},"94":{"start":{"line":307,"column":15},"end":{"line":307,"column":27}},"95":{"start":{"line":308,"column":2},"end":{"line":310,"column":3}},"96":{"start":{"line":309,"column":4},"end":{"line":309,"column":59}},"97":{"start":{"line":311,"column":2},"end":{"line":311,"column":46}},"98":{"start":{"line":319,"column":2},"end":{"line":319,"column":26}},"99":{"start":{"line":321,"column":2},"end":{"line":325,"column":3}},"100":{"start":{"line":322,"column":4},"end":{"line":322,"column":26}},"101":{"start":{"line":323,"column":4},"end":{"line":323,"column":58}},"102":{"start":{"line":324,"column":4},"end":{"line":324,"column":21}},"103":{"start":{"line":327,"column":2},"end":{"line":329,"column":3}},"104":{"start":{"line":328,"column":4},"end":{"line":328,"column":29}},"105":{"start":{"line":331,"column":2},"end":{"line":331,"column":35}},"106":{"start":{"line":338,"column":2},"end":{"line":338,"column":26}},"107":{"start":{"line":340,"column":2},"end":{"line":348,"column":3}},"108":{"start":{"line":341,"column":4},"end":{"line":341,"column":26}},"109":{"start":{"line":342,"column":4},"end":{"line":345,"column":5}},"110":{"start":{"line":343,"column":6},"end":{"line":343,"column":74}},"111":{"start":{"line":344,"column":6},"end":{"line":344,"column":46}},"112":{"start":{"line":346,"column":4},"end":{"line":346,"column":68}},"113":{"start":{"line":347,"column":4},"end":{"line":347,"column":22}},"114":{"start":{"line":355,"column":2},"end":{"line":357,"column":4}},"115":{"start":{"line":356,"column":4},"end":{"line":356,"column":79}},"116":{"start":{"line":358,"column":2},"end":{"line":358,"column":80}},"117":{"start":{"line":361,"column":0},"end":{"line":361,"column":57}},"118":{"start":{"line":363,"column":0},"end":{"line":378,"column":1}}},"fnMap":{"0":{"name":"EnvCmd","decl":{"start":{"line":24,"column":9},"end":{"line":24,"column":15}},"loc":{"start":{"line":24,"column":23},"end":{"line":64,"column":1}},"line":24},"1":{"name":"(anonymous_1)","decl":{"start":{"line":54,"column":28},"end":{"line":54,"column":29}},"loc":{"start":{"line":54,"column":38},"end":{"line":57,"column":3}},"line":54},"2":{"name":"ParseArgs","decl":{"start":{"line":72,"column":9},"end":{"line":72,"column":18}},"loc":{"start":{"line":72,"column":26},"end":{"line":108,"column":1}},"line":72},"3":{"name":"StripComments","decl":{"start":{"line":116,"column":9},"end":{"line":116,"column":22}},"loc":{"start":{"line":116,"column":35},"end":{"line":125,"column":1}},"line":116},"4":{"name":"StripEmptyLines","decl":{"start":{"line":133,"column":9},"end":{"line":133,"column":24}},"loc":{"start":{"line":133,"column":37},"end":{"line":136,"column":1}},"line":133},"5":{"name":"ParseEnvVars","decl":{"start":{"line":144,"column":9},"end":{"line":144,"column":21}},"loc":{"start":{"line":144,"column":34},"end":{"line":159,"column":1}},"line":144},"6":{"name":"ParseEnvString","decl":{"start":{"line":167,"column":9},"end":{"line":167,"column":23}},"loc":{"start":{"line":167,"column":40},"end":{"line":176,"column":1}},"line":167},"7":{"name":"ParseRCFile","decl":{"start":{"line":184,"column":9},"end":{"line":184,"column":20}},"loc":{"start":{"line":184,"column":32},"end":{"line":195,"column":1}},"line":184},"8":{"name":"UseRCFile","decl":{"start":{"line":204,"column":9},"end":{"line":204,"column":18}},"loc":{"start":{"line":204,"column":29},"end":{"line":226,"column":1}},"line":204},"9":{"name":"(anonymous_9)","decl":{"start":{"line":219,"column":19},"end":{"line":219,"column":20}},"loc":{"start":{"line":219,"column":35},"end":{"line":224,"column":3}},"line":219},"10":{"name":"UseCmdLine","decl":{"start":{"line":236,"column":9},"end":{"line":236,"column":19}},"loc":{"start":{"line":236,"column":30},"end":{"line":267,"column":1}},"line":236},"11":{"name":"PrintHelp","decl":{"start":{"line":273,"column":9},"end":{"line":273,"column":18}},"loc":{"start":{"line":273,"column":22},"end":{"line":286,"column":1}},"line":273},"12":{"name":"HandleUncaughtExceptions","decl":{"start":{"line":292,"column":9},"end":{"line":292,"column":33}},"loc":{"start":{"line":292,"column":38},"end":{"line":298,"column":1}},"line":292},"13":{"name":"ResolveEnvFilePath","decl":{"start":{"line":305,"column":9},"end":{"line":305,"column":27}},"loc":{"start":{"line":305,"column":39},"end":{"line":312,"column":1}},"line":305},"14":{"name":"TerminateSpawnedProc","decl":{"start":{"line":318,"column":9},"end":{"line":318,"column":29}},"loc":{"start":{"line":318,"column":51},"end":{"line":332,"column":1}},"line":318},"15":{"name":"TerminateParentProcess","decl":{"start":{"line":337,"column":9},"end":{"line":337,"column":31}},"loc":{"start":{"line":337,"column":47},"end":{"line":349,"column":1}},"line":337},"16":{"name":"RemoveProcessListeners","decl":{"start":{"line":354,"column":9},"end":{"line":354,"column":31}},"loc":{"start":{"line":354,"column":35},"end":{"line":359,"column":1}},"line":354},"17":{"name":"(anonymous_17)","decl":{"start":{"line":355,"column":28},"end":{"line":355,"column":29}},"loc":{"start":{"line":355,"column":38},"end":{"line":357,"column":3}},"line":355}},"branchMap":{"0":{"loc":{"start":{"line":30,"column":2},"end":{"line":35,"column":3}},"type":"if","locations":[{"start":{"line":30,"column":2},"end":{"line":35,"column":3}},{"start":{"line":30,"column":2},"end":{"line":35,"column":3}}],"line":30},"1":{"loc":{"start":{"line":39,"column":2},"end":{"line":44,"column":3}},"type":"if","locations":[{"start":{"line":39,"column":2},"end":{"line":44,"column":3}},{"start":{"line":39,"column":2},"end":{"line":44,"column":3}}],"line":39},"2":{"loc":{"start":{"line":73,"column":2},"end":{"line":75,"column":3}},"type":"if","locations":[{"start":{"line":73,"column":2},"end":{"line":75,"column":3}},{"start":{"line":73,"column":2},"end":{"line":75,"column":3}}],"line":73},"3":{"loc":{"start":{"line":84,"column":4},"end":{"line":87,"column":5}},"type":"if","locations":[{"start":{"line":84,"column":4},"end":{"line":87,"column":5}},{"start":{"line":84,"column":4},"end":{"line":87,"column":5}}],"line":84},"4":{"loc":{"start":{"line":88,"column":4},"end":{"line":91,"column":5}},"type":"if","locations":[{"start":{"line":88,"column":4},"end":{"line":91,"column":5}},{"start":{"line":88,"column":4},"end":{"line":91,"column":5}}],"line":88},"5":{"loc":{"start":{"line":93,"column":4},"end":{"line":98,"column":5}},"type":"if","locations":[{"start":{"line":93,"column":4},"end":{"line":98,"column":5}},{"start":{"line":93,"column":4},"end":{"line":98,"column":5}}],"line":93},"6":{"loc":{"start":{"line":151,"column":16},"end":{"line":151,"column":37}},"type":"binary-expr","locations":[{"start":{"line":151,"column":16},"end":{"line":151,"column":31}},{"start":{"line":151,"column":35},"end":{"line":151,"column":37}}],"line":151},"7":{"loc":{"start":{"line":210,"column":2},"end":{"line":217,"column":3}},"type":"if","locations":[{"start":{"line":210,"column":2},"end":{"line":217,"column":3}},{"start":{"line":210,"column":2},"end":{"line":217,"column":3}}],"line":210},"8":{"loc":{"start":{"line":210,"column":6},"end":{"line":210,"column":55}},"type":"binary-expr","locations":[{"start":{"line":210,"column":6},"end":{"line":210,"column":27}},{"start":{"line":210,"column":31},"end":{"line":210,"column":55}}],"line":210},"9":{"loc":{"start":{"line":221,"column":4},"end":{"line":223,"column":5}},"type":"if","locations":[{"start":{"line":221,"column":4},"end":{"line":223,"column":5}},{"start":{"line":221,"column":4},"end":{"line":223,"column":5}}],"line":221},"10":{"loc":{"start":{"line":244,"column":4},"end":{"line":246,"column":5}},"type":"if","locations":[{"start":{"line":244,"column":4},"end":{"line":246,"column":5}},{"start":{"line":244,"column":4},"end":{"line":246,"column":5}}],"line":244},"11":{"loc":{"start":{"line":250,"column":2},"end":{"line":256,"column":3}},"type":"if","locations":[{"start":{"line":250,"column":2},"end":{"line":256,"column":3}},{"start":{"line":250,"column":2},"end":{"line":256,"column":3}}],"line":250},"12":{"loc":{"start":{"line":250,"column":6},"end":{"line":250,"column":34}},"type":"binary-expr","locations":[{"start":{"line":250,"column":6},"end":{"line":250,"column":11}},{"start":{"line":250,"column":15},"end":{"line":250,"column":34}}],"line":250},"13":{"loc":{"start":{"line":262,"column":14},"end":{"line":264,"column":26}},"type":"cond-expr","locations":[{"start":{"line":263,"column":6},"end":{"line":263,"column":26}},{"start":{"line":264,"column":6},"end":{"line":264,"column":26}}],"line":262},"14":{"loc":{"start":{"line":262,"column":14},"end":{"line":262,"column":46}},"type":"binary-expr","locations":[{"start":{"line":262,"column":14},"end":{"line":262,"column":29}},{"start":{"line":262,"column":33},"end":{"line":262,"column":46}}],"line":262},"15":{"loc":{"start":{"line":293,"column":2},"end":{"line":295,"column":3}},"type":"if","locations":[{"start":{"line":293,"column":2},"end":{"line":295,"column":3}},{"start":{"line":293,"column":2},"end":{"line":295,"column":3}}],"line":293},"16":{"loc":{"start":{"line":308,"column":2},"end":{"line":310,"column":3}},"type":"if","locations":[{"start":{"line":308,"column":2},"end":{"line":310,"column":3}},{"start":{"line":308,"column":2},"end":{"line":310,"column":3}}],"line":308},"17":{"loc":{"start":{"line":321,"column":2},"end":{"line":325,"column":3}},"type":"if","locations":[{"start":{"line":321,"column":2},"end":{"line":325,"column":3}},{"start":{"line":321,"column":2},"end":{"line":325,"column":3}}],"line":321},"18":{"loc":{"start":{"line":327,"column":2},"end":{"line":329,"column":3}},"type":"if","locations":[{"start":{"line":327,"column":2},"end":{"line":329,"column":3}},{"start":{"line":327,"column":2},"end":{"line":329,"column":3}}],"line":327},"19":{"loc":{"start":{"line":340,"column":2},"end":{"line":348,"column":3}},"type":"if","locations":[{"start":{"line":340,"column":2},"end":{"line":348,"column":3}},{"start":{"line":340,"column":2},"end":{"line":348,"column":3}}],"line":340},"20":{"loc":{"start":{"line":342,"column":4},"end":{"line":345,"column":5}},"type":"if","locations":[{"start":{"line":342,"column":4},"end":{"line":345,"column":5}},{"start":{"line":342,"column":4},"end":{"line":345,"column":5}}],"line":342}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":13,"10":13,"11":7,"12":6,"13":10,"14":1,"15":9,"16":10,"17":10,"18":30,"19":30,"20":10,"21":10,"22":10,"23":10,"24":18,"25":1,"26":17,"27":17,"28":37,"29":37,"30":1,"31":1,"32":36,"33":2,"34":2,"35":34,"36":17,"37":17,"38":17,"39":17,"40":4,"41":4,"42":4,"43":4,"44":2,"45":2,"46":4,"47":4,"48":4,"49":13,"50":13,"51":13,"52":32,"53":32,"54":32,"55":32,"56":13,"57":3,"58":3,"59":3,"60":7,"61":7,"62":1,"63":1,"64":6,"65":7,"66":7,"67":6,"68":6,"69":6,"70":1,"71":1,"72":5,"73":8,"74":8,"75":7,"76":5,"77":6,"78":6,"79":6,"80":2,"81":1,"82":5,"83":1,"84":1,"85":1,"86":4,"87":4,"88":4,"89":2,"90":2,"91":1,"92":2,"93":2,"94":13,"95":13,"96":12,"97":13,"98":7,"99":7,"100":5,"101":5,"102":5,"103":7,"104":1,"105":6,"106":6,"107":6,"108":4,"109":4,"110":1,"111":1,"112":3,"113":3,"114":13,"115":39,"116":13,"117":1,"118":1},"f":{"0":13,"1":30,"2":18,"3":4,"4":4,"5":13,"6":3,"7":7,"8":7,"9":8,"10":6,"11":2,"12":2,"13":13,"14":7,"15":6,"16":13,"17":39},"b":{"0":[7,6],"1":[1,9],"2":[1,17],"3":[1,36],"4":[2,34],"5":[17,17],"6":[32,1],"7":[1,5],"8":[6,3],"9":[7,1],"10":[1,1],"11":[1,4],"12":[5,3],"13":[2,2],"14":[4,3],"15":[1,1],"16":[12,1],"17":[5,2],"18":[1,6],"19":[4,2],"20":[1,3]},"_coverageSchema":"332fd63041d2c1bcb487cc26dd0d5f7d97098a6c","hash":"8b5847db0e8855769a714d8c5ec023e8593c2f17","contentHash":"f87b8cb5f1f2bafcc70fd6954fffebb2_11.6.0"}}
'use strict'
const spawn = require('cross-spawn').spawn
const path = require('path')
const fs = require('fs')
const os = require('os')
const rcFileLocation = path.join(process.cwd(), '.env-cmdrc')
const envFilePathDefault = path.join(process.cwd(), '.env')
const terminateSpawnedProcessFuncHandlers = {}
let terminateProcessFuncHandler
const SIGNALS_TO_HANDLE = [
'SIGINT', 'SIGTERM', 'SIGHUP'
]
const sharedState = {
exitCalled: false
}
/**
* The main process for reading, parsing, applying and then running the process with env vars
* @param {Array<String>} args And array if strings representing cli args
*
* @return {Object} The child process
*/
function EnvCmd (args) {
// First Parse the args from the command line
const parsedArgs = ParseArgs(args)
// If a .rc file was found then use that
let parsedEnv
if (fs.existsSync(rcFileLocation)) {
parsedEnv = UseRCFile({ envFile: parsedArgs.envFile })
} else {
// Try to use a .env file
parsedEnv = UseCmdLine({ envFile: parsedArgs.envFile, useFallback: parsedArgs.useFallback })
}
let env
// Override the merge order if --no-override flag set
if (parsedArgs.noOverride) {
env = Object.assign({}, parsedEnv, process.env)
} else {
// Add in the system environment variables to our environment list
env = Object.assign({}, process.env, parsedEnv)
}
// Execute the command with the given environment variables
const proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
stdio: 'inherit',
env
})
// Handle a few special signals and then the general node exit event
// on both parent and spawned process
SIGNALS_TO_HANDLE.forEach(signal => {
terminateSpawnedProcessFuncHandlers[signal] = TerminateSpawnedProc.bind(sharedState, proc, signal)
process.once(signal, terminateSpawnedProcessFuncHandlers[signal])
})
process.once('exit', terminateSpawnedProcessFuncHandlers['SIGTERM'])
terminateProcessFuncHandler = TerminateParentProcess.bind(sharedState)
proc.on('exit', terminateProcessFuncHandler)
return proc
}
/**
* Parses the arguments passed into the cli
* @param {Array<String>} args An array of strings to parse the options out of
*
* @return {Object} An object containing cli options and commands
*/
function ParseArgs (args) {
if (args.length < 2) {
throw new Error('Error! Too few arguments passed to env-cmd.')
}
let envFile
let command
let noOverride
let useFallback
let commandArgs = args.slice()
while (commandArgs.length) {
const arg = commandArgs.shift()
if (arg === '--fallback') {
useFallback = true
continue
}
if (arg === '--no-override') {
noOverride = true
continue
}
// assume the first arg is the env file (or if using .rc the environment name)
if (!envFile) {
envFile = arg
} else {
command = arg
break
}
}
return {
envFile,
command,
commandArgs,
noOverride,
useFallback
}
}
/**
* Strips out comments from env file string
* @param {String} envString The .env file string
*
* @return {String} The .env file string with comments stripped out
*/
function StripComments (envString) {
const commentsRegex = /(^#.*$)/gim
let match = commentsRegex.exec(envString)
let newString = envString
while (match != null) {
newString = newString.replace(match[1], '')
match = commentsRegex.exec(envString)
}
return newString
}
/**
* Strips out newlines from env file string
* @param {String} envString The .env file string
*
* @return {String} The .env file string with newlines stripped out
*/
function StripEmptyLines (envString) {
const emptyLinesRegex = /(^\n)/gim
return envString.replace(emptyLinesRegex, '')
}
/**
* Parse out all env vars from an env file string
* @param {String} envString The .env file string
*
* @return {Object} Key/Value pairs corresponding to the .env file data
*/
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
const key = match[2].trim()
let value = match[3].trim() || ''
// remove any surrounding quotes
value = value.replace(/(^['"]|['"]$)/g, '')
matches[key] = value
}
return matches
}
/**
* Parse out all env vars from a given env file string and return an object
* @param {String} envString The .env file string
*
* @return {Object} Key/Value pairs of all env vars parsed from files
*/
function ParseEnvString (envFileString) {
// First thing we do is stripe out all comments
envFileString = StripComments(envFileString.toString())
// Next we stripe out all the empty lines
envFileString = StripEmptyLines(envFileString)
// Merge the file env vars with the current process env vars (the file vars overwrite process vars)
return ParseEnvVars(envFileString)
}
/**
* Reads and parses the .env-cmdrc file
* @param {String} fileData the .env-cmdrc file data (which should be a valid json string)
*
* @return {Object} The .env-cmdrc as a parsed JSON object
*/
function ParseRCFile (fileData) {
let data
try {
data = JSON.parse(fileData)
} catch (e) {
console.error(`Error:
Could not parse the .env-cmdrc file.
Please make sure its in a valid JSON format.`)
throw new Error(`Unable to parse JSON in .env-cmdrc file.`)
}
return data
}
/**
* Uses the rc file to get env vars
* @param {Object} options
* @param {String} options.envFile The .env-cmdrc file environment to use
*
* @return {Object} Key/Value pair of env vars from the .env-cmdrc file
*/
function UseRCFile (options) {
const fileData = fs.readFileSync(rcFileLocation, { encoding: 'utf8' })
const parsedData = ParseRCFile(fileData)
let result = {}
const envNames = options.envFile.split(',')
if (envNames.length === 1 && !parsedData[envNames[0]]) {
console.error(`Error:
Could not find environment:
${options.envFile}
in .rc file:
${rcFileLocation}`)
throw new Error(`Missing environment ${options.envFile} in .env-cmdrc file.`)
}
envNames.forEach(function (name) {
const envVars = parsedData[name]
if (envVars) {
result = Object.assign(result, envVars)
}
})
return result
}
/**
* Uses the cli passed env file to get env vars
* @param {Object} options
* @param {String} options.envFile The .env file name/relative path
* @param {Boolean} options.useFallback Should we attempt to find a fallback file
*
* @return {Object} Key/Value pairing of env vars found in .env file
*/
function UseCmdLine (options) {
const envFilePath = ResolveEnvFilePath(options.envFile)
// Attempt to open the provided file
let file
try {
file = fs.readFileSync(envFilePath, { encoding: 'utf8' })
} catch (err) {
if (!options.useFallback) {
return {}
}
}
// If we don't have a main file try the fallback file
if (!file && options.useFallback) {
try {
file = fs.readFileSync(envFilePathDefault)
} catch (e) {
throw new Error(`Error! Could not find fallback file or read env file at ${envFilePathDefault}`)
}
}
// Get the file extension
const ext = path.extname(envFilePath).toLowerCase()
// Parse the env file string using the correct parser
const env = ext === '.json' || ext === '.js'
? require(envFilePath)
: ParseEnvString(file)
return env
}
/**
* Prints out some minor help text
* @return {String} Help text
*/
function PrintHelp () {
return `
Usage: env-cmd [options] [env_file | env_name] command [command options]
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.
Options:
--no-override - do not override existing process env vars with file env vars
--fallback - if provided env file does not exist, attempt to use fallback .env file in root dir
`
}
/**
* General exception handler
* @param {Error} e The exception error to handle
*/
function HandleUncaughtExceptions (e) {
if (e.message.match(/passed/gi)) {
console.log(PrintHelp())
}
console.log(e.message)
process.exit(1)
}
/**
* A simple function for resolving the path the user entered
* @param {String} userPath A path
* @return {String} The fully qualified absolute path
*/
function ResolveEnvFilePath (userPath) {
// Make sure a home directory exist
const home = os.homedir()
if (home) {
userPath = userPath.replace(/^~($|\/|\\)/, `${home}$1`)
}
return path.resolve(process.cwd(), userPath)
}
/**
* Helper for terminating the spawned process
* @param {ProccessHandler} proc The spawned process handler
*/
function TerminateSpawnedProc (proc, signal, code) {
RemoveProcessListeners()
if (!this.exitCalled) {
this.exitCalled = true
proc.kill(signal)
}
if (code) {
return process.exit(code)
}
process.kill(process.pid, signal)
}
/**
* Helper for terminating the parent process
*/
function TerminateParentProcess (code, signal) {
RemoveProcessListeners()
if (!this.exitCalled) {
this.exitCalled = true
if (signal) {
return process.kill(process.pid, signal)
}
process.exit(code)
}
}
/**
* Helper for removing all termination signal listeners from parent process
*/
function RemoveProcessListeners () {
SIGNALS_TO_HANDLE.forEach(signal => {
process.removeListener(signal, terminateSpawnedProcessFuncHandlers[signal])
})
process.removeListener('exit', terminateSpawnedProcessFuncHandlers['SIGTERM'])
}
process.on('uncaughtException', HandleUncaughtExceptions)
module.exports = {
EnvCmd,
ParseArgs,
ParseEnvString,
PrintHelp,
HandleUncaughtExceptions,
TerminateSpawnedProc,
TerminateParentProcess,
StripComments,
StripEmptyLines,
ParseEnvVars,
ParseRCFile,
UseRCFile,
UseCmdLine,
ResolveEnvFilePath
}