mirror of
https://github.com/toddbluhm/env-cmd.git
synced 2025-12-08 18:23:33 +00:00
Initial conversion of lib over to typescript
- All env-cmd-examples repo cases passing - Added support for default .env-cmdrc.json file - Added flag and help text lib - Split up project into more reasonable files/chunks of code - Updated copyright year to 2019
This commit is contained in:
parent
9fcfd621dd
commit
0738042dbb
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Todd Bluhm
|
||||
Copyright (c) 2019 Todd Bluhm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
10
lib/help.js
Normal file
10
lib/help.js
Normal file
@ -0,0 +1,10 @@
|
||||
"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;
|
||||
393
lib/index.js
393
lib/index.js
@ -1,375 +1,32 @@
|
||||
'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
|
||||
}
|
||||
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var spawn = require("cross-spawn");
|
||||
var signal_termination_1 = require("./signal-termination");
|
||||
var parse_args_1 = require("./parse-args");
|
||||
/**
|
||||
* 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
|
||||
function EnvCmd(args) {
|
||||
// First Parse the args from the command line
|
||||
var parsedArgs = parse_args_1.parseArgs(args);
|
||||
var env;
|
||||
// Override the merge order if --no-override flag set
|
||||
if (parsedArgs.options.noOverride) {
|
||||
env = Object.assign({}, parsedArgs.envValues, process.env);
|
||||
}
|
||||
if (arg === '--no-override') {
|
||||
noOverride = true
|
||||
continue
|
||||
else {
|
||||
// Add in the system environment variables to our environment list
|
||||
env = Object.assign({}, process.env, parsedArgs.envValues);
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// Execute the command with the given environment variables
|
||||
var proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
|
||||
stdio: 'inherit',
|
||||
env: env
|
||||
});
|
||||
// Handle any termination signals for parent and child proceses
|
||||
signal_termination_1.handleTermSignals(proc);
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
EnvCmd: EnvCmd
|
||||
};
|
||||
|
||||
99
lib/parse-args.js
Normal file
99
lib/parse-args.js
Normal file
@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var program = require("commander");
|
||||
var utils_1 = require("./utils");
|
||||
var parse_rc_file_1 = require("./parse-rc-file");
|
||||
var parse_env_file_1 = require("./parse-env-file");
|
||||
var RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.json'];
|
||||
var ENV_FILE_DEFAULT_LOCATION = './.env';
|
||||
/**
|
||||
* Parses the arguments passed into the cli
|
||||
*/
|
||||
function parseArgs(args) {
|
||||
program
|
||||
.version('1.0.0', '-v, --version')
|
||||
.usage('[options] <command> [...args]')
|
||||
.option('-f, --file [path]', 'Custom .env file location')
|
||||
.option('-r, --rc-file [path]', 'Custom .env-cmdrc file location')
|
||||
.option('-e, --environments [env...]', 'The rc-file environment to select', utils_1.parseArgList)
|
||||
.option('--fallback', 'Enables auto fallback to default env file location')
|
||||
.option('--no-override', 'Do not override existing env vars')
|
||||
.parse(['_', '_'].concat(args));
|
||||
// get the command and command args
|
||||
var command = program.args[0];
|
||||
var commandArgs = program.args.slice(1);
|
||||
var noOverride = !program.override;
|
||||
var returnValue = {
|
||||
command: command,
|
||||
commandArgs: commandArgs,
|
||||
options: {
|
||||
noOverride: noOverride
|
||||
}
|
||||
};
|
||||
// Check for rc file usage
|
||||
var env;
|
||||
if (program.environments) {
|
||||
// user provided an .rc file path
|
||||
if (program.rcFile) {
|
||||
try {
|
||||
env = parse_rc_file_1.useRCFile({ environments: program.environments, path: program.rcFile });
|
||||
}
|
||||
catch (e) {
|
||||
console.log(program.outputHelp());
|
||||
throw new Error("Unable to locate .rc file at location (" + program.rcFile + ")");
|
||||
}
|
||||
// Use the default .rc file locations
|
||||
}
|
||||
else {
|
||||
for (var _i = 0, RC_FILE_DEFAULT_LOCATIONS_1 = RC_FILE_DEFAULT_LOCATIONS; _i < RC_FILE_DEFAULT_LOCATIONS_1.length; _i++) {
|
||||
var path = RC_FILE_DEFAULT_LOCATIONS_1[_i];
|
||||
try {
|
||||
env = parse_rc_file_1.useRCFile({ environments: program.environments, path: path });
|
||||
break;
|
||||
}
|
||||
catch (e) { }
|
||||
}
|
||||
if (!env) {
|
||||
console.log(program.outputHelp());
|
||||
throw new Error("Unable to locate .rc file at default locations (" + RC_FILE_DEFAULT_LOCATIONS + ")");
|
||||
}
|
||||
}
|
||||
if (env) {
|
||||
returnValue.envValues = env;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
// Use env file
|
||||
if (program.file) {
|
||||
try {
|
||||
env = parse_env_file_1.useCmdLine(program.file);
|
||||
}
|
||||
catch (e) {
|
||||
if (!program.fallback) {
|
||||
console.log(program.outputHelp());
|
||||
console.error("Unable to locate env file at location (" + program.file + ")");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (env) {
|
||||
returnValue.envValues = env;
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
// Use the default env file location
|
||||
try {
|
||||
env = parse_env_file_1.useCmdLine(ENV_FILE_DEFAULT_LOCATION);
|
||||
}
|
||||
catch (e) {
|
||||
console.log(program.outputHelp());
|
||||
console.error("Unable to locate env file at default locations (" + ENV_FILE_DEFAULT_LOCATION + ")");
|
||||
throw e;
|
||||
}
|
||||
if (env) {
|
||||
returnValue.envValues = env;
|
||||
return returnValue;
|
||||
}
|
||||
console.log(program.outputHelp());
|
||||
throw Error('Unable to locate any files to read environment data!');
|
||||
}
|
||||
exports.parseArgs = parseArgs;
|
||||
77
lib/parse-env-file.js
Normal file
77
lib/parse-env-file.js
Normal file
@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var utils_1 = require("./utils");
|
||||
/**
|
||||
* Uses the cli passed env file path to get env vars
|
||||
*/
|
||||
function useCmdLine(envFilePath) {
|
||||
var absolutePath = utils_1.resolveEnvFilePath(envFilePath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
throw new Error("Invalid env file path (" + envFilePath + ").");
|
||||
}
|
||||
// Get the file extension
|
||||
var ext = path.extname(absolutePath).toLowerCase();
|
||||
var env;
|
||||
if (ext === '.json') {
|
||||
env = require(absolutePath);
|
||||
}
|
||||
else {
|
||||
var file = fs.readFileSync(absolutePath, { encoding: 'utf8' });
|
||||
env = parseEnvString(file);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
exports.useCmdLine = useCmdLine;
|
||||
/**
|
||||
* 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) {
|
||||
var envParseRegex = /^((.+?)[=](.*))$/gim;
|
||||
var matches = {};
|
||||
var match;
|
||||
while ((match = envParseRegex.exec(envString)) !== null) {
|
||||
// Note: match[1] is the full env=var line
|
||||
var key = match[2].trim();
|
||||
var value = match[3].trim() || '';
|
||||
// remove any surrounding quotes
|
||||
matches[key] = value.replace(/(^['"]|['"]$)/g, '');
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
exports.parseEnvVars = parseEnvVars;
|
||||
/**
|
||||
* Strips out comments from env file string
|
||||
*/
|
||||
function stripComments(envString) {
|
||||
var commentsRegex = /(^#.*$)/gim;
|
||||
var match = commentsRegex.exec(envString);
|
||||
var 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) {
|
||||
var emptyLinesRegex = /(^\n)/gim;
|
||||
return envString.replace(emptyLinesRegex, '');
|
||||
}
|
||||
exports.stripEmptyLines = stripEmptyLines;
|
||||
63
lib/parse-rc-file.js
Normal file
63
lib/parse-rc-file.js
Normal file
@ -0,0 +1,63 @@
|
||||
"use strict";
|
||||
var __assign = (this && this.__assign) || function () {
|
||||
__assign = Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var fs = require("fs");
|
||||
var utils_1 = require("./utils");
|
||||
/**
|
||||
* Uses the rc file and rc environment to get env vars
|
||||
*/
|
||||
function useRCFile(_a) {
|
||||
var environments = _a.environments, path = _a.path;
|
||||
var absolutePath = utils_1.resolveEnvFilePath(path);
|
||||
console.log(absolutePath);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
throw new Error('Invalid .rc file path.');
|
||||
}
|
||||
var fileData = fs.readFileSync(absolutePath, { encoding: 'utf8' });
|
||||
var parsedData = parseRCFile(fileData);
|
||||
if (environments.length === 1 && !parsedData[environments[0]]) {
|
||||
console.error("Error:\n Could not find environment:\n " + environments[0] + "\n in .rc file:\n " + absolutePath);
|
||||
throw new Error("Missing environment " + environments[0] + " in .env-cmdrc file.");
|
||||
}
|
||||
// Parse and merge multiple rc environments together
|
||||
var result = {};
|
||||
var environmentFound = false;
|
||||
environments.forEach(function (name) {
|
||||
var envVars = parsedData[name];
|
||||
if (envVars) {
|
||||
environmentFound = true;
|
||||
result = __assign({}, result, envVars);
|
||||
}
|
||||
});
|
||||
if (!environmentFound) {
|
||||
console.error("Error:\n Could not find any environments:\n " + environments + "\n in .rc file:\n " + absolutePath);
|
||||
throw new Error("All environments (" + environments + ") are missing in in .rc file (" + absolutePath + ").");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.useRCFile = useRCFile;
|
||||
/**
|
||||
* Reads and parses the .env-cmdrc file
|
||||
*/
|
||||
function parseRCFile(fileData) {
|
||||
var data;
|
||||
try {
|
||||
data = JSON.parse(fileData);
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error:\n Could not parse the .env-cmdrc file.\n Please make sure its in a valid JSON format.");
|
||||
throw new Error("Unable to parse JSON in .env-cmdrc file.");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
exports.parseRCFile = parseRCFile;
|
||||
74
lib/signal-termination.js
Normal file
74
lib/signal-termination.js
Normal file
@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var program = require("commander");
|
||||
var SIGNALS_TO_HANDLE = [
|
||||
'SIGINT', 'SIGTERM', 'SIGHUP'
|
||||
];
|
||||
var terminateSpawnedProcessFuncHandlers = {};
|
||||
var sharedState = {
|
||||
exitCalled: false
|
||||
};
|
||||
function handleTermSignals(proc) {
|
||||
// Handle a few special signals and then the general node exit event
|
||||
// on both parent and spawned process
|
||||
SIGNALS_TO_HANDLE.forEach(function (signal) {
|
||||
terminateSpawnedProcessFuncHandlers[signal] =
|
||||
function (signal, code) { return terminateSpawnedProc(proc, signal, sharedState, code); };
|
||||
process.once(signal, terminateSpawnedProcessFuncHandlers[signal]);
|
||||
});
|
||||
process.once('exit', terminateSpawnedProcessFuncHandlers['SIGTERM']);
|
||||
var terminateProcessFuncHandler = function (code, signal) { return terminateParentProcess(code, signal, sharedState); };
|
||||
proc.on('exit', terminateProcessFuncHandler);
|
||||
}
|
||||
exports.handleTermSignals = handleTermSignals;
|
||||
/**
|
||||
* Helper for terminating the spawned process
|
||||
*/
|
||||
function terminateSpawnedProc(proc, signal, sharedState, code) {
|
||||
removeProcessListeners();
|
||||
if (!sharedState.exitCalled) {
|
||||
sharedState.exitCalled = true;
|
||||
proc.kill(signal);
|
||||
}
|
||||
if (code) {
|
||||
return process.exit(code);
|
||||
}
|
||||
process.kill(process.pid, signal);
|
||||
}
|
||||
exports.terminateSpawnedProc = terminateSpawnedProc;
|
||||
/**
|
||||
* Helper for terminating the parent process
|
||||
*/
|
||||
function terminateParentProcess(code, signal, sharedState) {
|
||||
removeProcessListeners();
|
||||
if (!sharedState.exitCalled) {
|
||||
sharedState.exitCalled = true;
|
||||
if (signal) {
|
||||
return process.kill(process.pid, signal);
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
}
|
||||
exports.terminateParentProcess = terminateParentProcess;
|
||||
/**
|
||||
* Helper for removing all termination signal listeners from parent process
|
||||
*/
|
||||
function removeProcessListeners() {
|
||||
SIGNALS_TO_HANDLE.forEach(function (signal) {
|
||||
process.removeListener(signal, terminateSpawnedProcessFuncHandlers[signal]);
|
||||
});
|
||||
process.removeListener('exit', terminateSpawnedProcessFuncHandlers['SIGTERM']);
|
||||
}
|
||||
exports.removeProcessListeners = removeProcessListeners;
|
||||
/**
|
||||
* General exception handler
|
||||
*/
|
||||
function handleUncaughtExceptions(e) {
|
||||
if (e.message.match(/passed/gi)) {
|
||||
console.log(program.outputHelp());
|
||||
}
|
||||
console.log(e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
exports.handleUncaughtExceptions = handleUncaughtExceptions;
|
||||
process.on('uncaughtException', handleUncaughtExceptions);
|
||||
23
lib/utils.js
Normal file
23
lib/utils.js
Normal file
@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
/**
|
||||
* A simple function for resolving the path the user entered
|
||||
*/
|
||||
function resolveEnvFilePath(userPath) {
|
||||
// Make sure a home directory exist
|
||||
var 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;
|
||||
22
package.json
22
package.json
@ -10,11 +10,12 @@
|
||||
"env-cmd": "bin/env-cmd.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"test": "mocha -r ts-node/register ./**/*.ts",
|
||||
"test-cover": "nyc --reporter=lcov --reporter=text npm test",
|
||||
"test-lint": "standard",
|
||||
"coveralls": "coveralls < coverage/lcov.info",
|
||||
"lint": "standard --fix"
|
||||
"lint": "standard --env mocha --fix ./**/*.ts",
|
||||
"build": "tsc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -41,15 +42,28 @@
|
||||
},
|
||||
"homepage": "https://github.com/toddbluhm/env-cmd#readme",
|
||||
"dependencies": {
|
||||
"commander": "^2.19.0",
|
||||
"cross-spawn": "^6.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/node": "^10.12.20",
|
||||
"@typescript-eslint/eslint-plugin": "^1.1.1",
|
||||
"@typescript-eslint/parser": "^1.1.1",
|
||||
"better-assert": "^1.0.2",
|
||||
"coveralls": "^3.0.1",
|
||||
"mocha": "^5.1.1",
|
||||
"nyc": "^13.1.0",
|
||||
"proxyquire": "^2.0.1",
|
||||
"sinon": "^7.0.0",
|
||||
"standard": "^12.0.1"
|
||||
"sinon": "^7.2.3",
|
||||
"standard": "^12.0.1",
|
||||
"ts-node": "^8.0.2",
|
||||
"typescript": "~3.2.1"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
35
src/index.ts
Normal file
35
src/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as spawn from 'cross-spawn'
|
||||
import { handleTermSignals } from './signal-termination'
|
||||
import { parseArgs } from './parse-args'
|
||||
|
||||
/**
|
||||
* The main process for reading, parsing, applying and then running the process with env vars
|
||||
*/
|
||||
function EnvCmd (args: string[]): { [key: string]: any } {
|
||||
// First Parse the args from the command line
|
||||
const parsedArgs = parseArgs(args)
|
||||
|
||||
let env
|
||||
// Override the merge order if --no-override flag set
|
||||
if (parsedArgs.options.noOverride) {
|
||||
env = Object.assign({}, parsedArgs.envValues, process.env)
|
||||
} else {
|
||||
// Add in the system environment variables to our environment list
|
||||
env = Object.assign({}, process.env, parsedArgs.envValues)
|
||||
}
|
||||
|
||||
// Execute the command with the given environment variables
|
||||
const proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
|
||||
stdio: 'inherit',
|
||||
env
|
||||
})
|
||||
|
||||
// Handle any termination signals for parent and child proceses
|
||||
handleTermSignals(proc)
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
EnvCmd
|
||||
}
|
||||
110
src/parse-args.ts
Normal file
110
src/parse-args.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import * as program from 'commander'
|
||||
import { parseArgList } from './utils'
|
||||
import { useRCFile } from './parse-rc-file'
|
||||
import { useCmdLine } from './parse-env-file'
|
||||
|
||||
const RC_FILE_DEFAULT_LOCATIONS = ['./.env-cmdrc', './.env-cmdrc.json']
|
||||
const ENV_FILE_DEFAULT_LOCATION = './.env'
|
||||
|
||||
type parseArgsReturnVal = {
|
||||
envValues: { [key: string]: any }
|
||||
command: string
|
||||
commandArgs: string[]
|
||||
options: {
|
||||
noOverride: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the arguments passed into the cli
|
||||
*/
|
||||
export function parseArgs (args: string[]): parseArgsReturnVal {
|
||||
program
|
||||
.version('1.0.0', '-v, --version')
|
||||
.usage('[options] <command> [...args]')
|
||||
.option('-f, --file [path]', 'Custom .env file location')
|
||||
.option('-r, --rc-file [path]', 'Custom .env-cmdrc file location')
|
||||
.option('-e, --environments [env...]', 'The rc-file environment to select', parseArgList)
|
||||
.option('--fallback', 'Enables auto fallback to default env file location')
|
||||
.option('--no-override', 'Do not override existing env vars')
|
||||
.parse(['_', '_', ...args])
|
||||
|
||||
// get the command and command args
|
||||
const command = program.args[0]
|
||||
const commandArgs = program.args.slice(1)
|
||||
const noOverride = !program.override
|
||||
|
||||
const returnValue: any = {
|
||||
command,
|
||||
commandArgs,
|
||||
options: {
|
||||
noOverride
|
||||
}
|
||||
}
|
||||
|
||||
// Check for rc file usage
|
||||
let env
|
||||
if (program.environments) {
|
||||
// user provided an .rc file path
|
||||
if (program.rcFile) {
|
||||
try {
|
||||
env = useRCFile({ environments: program.environments, path: program.rcFile })
|
||||
} catch (e) {
|
||||
console.log(program.outputHelp())
|
||||
throw new Error(`Unable to locate .rc file at location (${program.rcFile})`)
|
||||
}
|
||||
// Use the default .rc file locations
|
||||
} else {
|
||||
for (const path of RC_FILE_DEFAULT_LOCATIONS) {
|
||||
try {
|
||||
env = useRCFile({ environments: program.environments, path })
|
||||
break
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!env) {
|
||||
console.log(program.outputHelp())
|
||||
throw new Error(`Unable to locate .rc file at default locations (${RC_FILE_DEFAULT_LOCATIONS})`)
|
||||
}
|
||||
}
|
||||
|
||||
if (env) {
|
||||
returnValue.envValues = env
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
|
||||
// Use env file
|
||||
if (program.file) {
|
||||
try {
|
||||
env = useCmdLine(program.file)
|
||||
} catch (e) {
|
||||
if (!program.fallback) {
|
||||
console.log(program.outputHelp())
|
||||
console.error(`Unable to locate env file at location (${program.file})`)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
if (env) {
|
||||
returnValue.envValues = env
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
|
||||
// Use the default env file location
|
||||
try {
|
||||
env = useCmdLine(ENV_FILE_DEFAULT_LOCATION)
|
||||
} catch (e) {
|
||||
console.log(program.outputHelp())
|
||||
console.error(`Unable to locate env file at default locations (${ENV_FILE_DEFAULT_LOCATION})`)
|
||||
throw e
|
||||
}
|
||||
|
||||
if (env) {
|
||||
returnValue.envValues = env
|
||||
return returnValue
|
||||
}
|
||||
|
||||
console.log(program.outputHelp())
|
||||
throw Error('Unable to locate any files to read environment data!')
|
||||
}
|
||||
79
src/parse-env-file.ts
Normal file
79
src/parse-env-file.ts
Normal file
@ -0,0 +1,79 @@
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { resolveEnvFilePath } from './utils'
|
||||
|
||||
/**
|
||||
* Uses the cli passed env file path to get env vars
|
||||
*/
|
||||
export function useCmdLine (envFilePath: string): { [key: string]: any } {
|
||||
const absolutePath = 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 (ext === '.json') {
|
||||
env = require(absolutePath)
|
||||
} else {
|
||||
const file = fs.readFileSync(absolutePath, { encoding: 'utf8' })
|
||||
env = parseEnvString(file)
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out all env vars from a given env file string and return an object
|
||||
*/
|
||||
export function parseEnvString (envFileString: string): { [key: string]: string } {
|
||||
// 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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse out all env vars from an env file string
|
||||
*/
|
||||
export function parseEnvVars (envString: string): { [key: string]: string } {
|
||||
const envParseRegex = /^((.+?)[=](.*))$/gim
|
||||
const matches: { [key: string]: string } = {}
|
||||
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, '')
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips out comments from env file string
|
||||
*/
|
||||
export function stripComments (envString: string): string {
|
||||
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
|
||||
*/
|
||||
export function stripEmptyLines (envString: string): string {
|
||||
const emptyLinesRegex = /(^\n)/gim
|
||||
return envString.replace(emptyLinesRegex, '')
|
||||
}
|
||||
68
src/parse-rc-file.ts
Normal file
68
src/parse-rc-file.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import * as fs from 'fs'
|
||||
import { resolveEnvFilePath } from './utils'
|
||||
|
||||
/**
|
||||
* Uses the rc file and rc environment to get env vars
|
||||
*/
|
||||
export function useRCFile (
|
||||
{ environments, path }:
|
||||
{ environments: string[], path: string }
|
||||
): { [key: string]: any } {
|
||||
const absolutePath = resolveEnvFilePath(path)
|
||||
console.log(absolutePath)
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
throw new Error('Invalid .rc file path.')
|
||||
}
|
||||
const fileData = fs.readFileSync(absolutePath, { encoding: 'utf8' })
|
||||
const parsedData = parseRCFile(fileData)
|
||||
|
||||
if (environments.length === 1 && !parsedData[environments[0]]) {
|
||||
console.error(`Error:
|
||||
Could not find environment:
|
||||
${environments[0]}
|
||||
in .rc file:
|
||||
${absolutePath}`)
|
||||
throw new Error(`Missing environment ${environments[0]} in .env-cmdrc 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 = {
|
||||
...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
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and parses the .env-cmdrc file
|
||||
*/
|
||||
export function parseRCFile (fileData: string): { [key: string]: any } {
|
||||
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
|
||||
}
|
||||
84
src/signal-termination.ts
Normal file
84
src/signal-termination.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { ChildProcess } from 'child_process' // eslint-disable-line
|
||||
import * as program from 'commander'
|
||||
|
||||
const SIGNALS_TO_HANDLE: NodeJS.Signals[] = [
|
||||
'SIGINT', 'SIGTERM', 'SIGHUP'
|
||||
]
|
||||
|
||||
const terminateSpawnedProcessFuncHandlers: {[key: string]: any } = {}
|
||||
const sharedState = {
|
||||
exitCalled: false
|
||||
}
|
||||
|
||||
export function handleTermSignals (proc: ChildProcess) {
|
||||
// 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] =
|
||||
(signal: any, code: any) => terminateSpawnedProc(proc, signal, sharedState, code)
|
||||
process.once(signal, terminateSpawnedProcessFuncHandlers[signal])
|
||||
})
|
||||
process.once('exit', terminateSpawnedProcessFuncHandlers['SIGTERM'])
|
||||
|
||||
const terminateProcessFuncHandler =
|
||||
(code: any, signal: any) => terminateParentProcess(code, signal, sharedState)
|
||||
proc.on('exit', terminateProcessFuncHandler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for terminating the spawned process
|
||||
*/
|
||||
export function terminateSpawnedProc (
|
||||
proc: ChildProcess, signal: string, sharedState: any, code?: number
|
||||
) {
|
||||
removeProcessListeners()
|
||||
|
||||
if (!sharedState.exitCalled) {
|
||||
sharedState.exitCalled = true
|
||||
proc.kill(signal)
|
||||
}
|
||||
|
||||
if (code) {
|
||||
return process.exit(code)
|
||||
}
|
||||
|
||||
process.kill(process.pid, signal)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for terminating the parent process
|
||||
*/
|
||||
export function terminateParentProcess (code: number, signal: string, sharedState: any) {
|
||||
removeProcessListeners()
|
||||
|
||||
if (!sharedState.exitCalled) {
|
||||
sharedState.exitCalled = true
|
||||
if (signal) {
|
||||
return process.kill(process.pid, signal)
|
||||
}
|
||||
process.exit(code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for removing all termination signal listeners from parent process
|
||||
*/
|
||||
export function removeProcessListeners () {
|
||||
SIGNALS_TO_HANDLE.forEach(signal => {
|
||||
process.removeListener(signal, terminateSpawnedProcessFuncHandlers[signal])
|
||||
})
|
||||
process.removeListener('exit', terminateSpawnedProcessFuncHandlers['SIGTERM'])
|
||||
}
|
||||
|
||||
/**
|
||||
* General exception handler
|
||||
*/
|
||||
export function handleUncaughtExceptions (e: Error) {
|
||||
if (e.message.match(/passed/gi)) {
|
||||
console.log(program.outputHelp())
|
||||
}
|
||||
console.log(e.message)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
process.on('uncaughtException', handleUncaughtExceptions)
|
||||
20
src/utils.ts
Normal file
20
src/utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as path from 'path'
|
||||
import * as os from 'os'
|
||||
|
||||
/**
|
||||
* A simple function for resolving the path the user entered
|
||||
*/
|
||||
export function resolveEnvFilePath (userPath: string): string {
|
||||
// Make sure a home directory exist
|
||||
const home = os.homedir()
|
||||
if (home) {
|
||||
userPath = userPath.replace(/^~($|\/|\\)/, `${home}$1`)
|
||||
}
|
||||
return path.resolve(process.cwd(), userPath)
|
||||
}
|
||||
/**
|
||||
* A simple function that parses a comma separated string into an array of strings
|
||||
*/
|
||||
export function parseArgList (list: string): string[] {
|
||||
return list.split(',')
|
||||
}
|
||||
417
test/index.ts
Normal file
417
test/index.ts
Normal file
@ -0,0 +1,417 @@
|
||||
// import * as assert from 'better-assert'
|
||||
// import { describe, it, afterEach, beforeEach, before, after } from 'mocha'
|
||||
// import * as path from 'path'
|
||||
// import * as proxyquire from 'proxyquire'
|
||||
// import * as sinon from 'sinon'
|
||||
// import * as fs from 'fs'
|
||||
|
||||
// let userHomeDir = '/Users/hitchhikers-guide-to-the-galaxy'
|
||||
// const spawnStub = sinon.spy(() => ({
|
||||
// on: sinon.stub(),
|
||||
// exit: sinon.stub(),
|
||||
// kill: sinon.stub()
|
||||
// }))
|
||||
|
||||
// const lib = proxyquire('../lib', {
|
||||
// 'cross-spawn': {
|
||||
// spawn: spawnStub
|
||||
// },
|
||||
// [path.resolve(process.cwd(), 'test/.env.json')]: {
|
||||
// BOB: 'COOL',
|
||||
// NODE_ENV: 'dev',
|
||||
// ANSWER: '42'
|
||||
// },
|
||||
// [path.resolve(process.cwd(), 'test/.env.js')]: {
|
||||
// BOB: 'COOL',
|
||||
// NODE_ENV: 'dev',
|
||||
// ANSWER: '42'
|
||||
// },
|
||||
// 'os': {
|
||||
// homedir: () => userHomeDir
|
||||
// }
|
||||
// })
|
||||
// const EnvCmd = lib.EnvCmd
|
||||
// const parseArgs = lib.parseArgs
|
||||
// const ParseEnvString = lib.ParseEnvString
|
||||
// const StripComments = lib.StripComments
|
||||
// const StripEmptyLines = lib.StripEmptyLines
|
||||
// const ParseEnvVars = lib.ParseEnvVars
|
||||
// const ResolveEnvFilePath = lib.ResolveEnvFilePath
|
||||
|
||||
// describe('env-cmd', function () {
|
||||
// describe('parseArgs', function () {
|
||||
// it('should parse out --no-override option ', function () {
|
||||
// const parsedArgs = parseArgs(['--no-override', './test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.option. === true)
|
||||
// })
|
||||
|
||||
// it('should parse out the envfile', function () {
|
||||
// const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.envFile === './test/envFile')
|
||||
// })
|
||||
|
||||
// it('should parse out the command', function () {
|
||||
// const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.command === 'command')
|
||||
// })
|
||||
|
||||
// it('should parse out the command args', function () {
|
||||
// const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.commandArgs.length === 2)
|
||||
// assert(parsedArgs.commandArgs[0] === 'cmda1')
|
||||
// assert(parsedArgs.commandArgs[1] === 'cmda2')
|
||||
// })
|
||||
|
||||
// it('should error out if incorrect number of args passed', function () {
|
||||
// try {
|
||||
// ParseArgs(['./test/envFile'])
|
||||
// } catch (e) {
|
||||
// assert(e.message === 'Error! Too few arguments passed to env-cmd.')
|
||||
// return
|
||||
// }
|
||||
// assert(!'No exception thrown')
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('ParseEnvString', function () {
|
||||
// it('should parse env vars and merge (overwrite) with process.env vars', function () {
|
||||
// const env = ParseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
|
||||
// assert(env.BOB === 'COOL')
|
||||
// assert(env.NODE_ENV === 'dev')
|
||||
// assert(env.ANSWER === '42')
|
||||
// })
|
||||
// })
|
||||
|
||||
// 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')
|
||||
// })
|
||||
// })
|
||||
|
||||
// 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 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 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')
|
||||
// })
|
||||
|
||||
// it('should default an empty value to an empty string', function () {
|
||||
// const envVars = ParseEnvVars('EMPTY=\n')
|
||||
// assert(envVars.EMPTY === '')
|
||||
// })
|
||||
|
||||
// it('should escape double quoted values', function () {
|
||||
// const envVars = ParseEnvVars('DOUBLE_QUOTES="double_quotes"\n')
|
||||
// assert(envVars.DOUBLE_QUOTES === 'double_quotes')
|
||||
// })
|
||||
|
||||
// it('should escape single quoted values', function () {
|
||||
// const envVars = ParseEnvVars('SINGLE_QUOTES=\'single_quotes\'\n')
|
||||
// assert(envVars.SINGLE_QUOTES === 'single_quotes')
|
||||
// })
|
||||
|
||||
// it('should preserve embedded double quotes', function () {
|
||||
// const envVars = ParseEnvVars('DOUBLE=""""\nDOUBLE_ONE=\'"double_one"\'\nDOUBLE_TWO=""double_two""\n')
|
||||
// assert(envVars.DOUBLE === '""')
|
||||
// assert(envVars.DOUBLE_ONE === '"double_one"')
|
||||
// assert(envVars.DOUBLE_TWO === '"double_two"')
|
||||
// })
|
||||
|
||||
// it('should preserve embedded single quotes', function () {
|
||||
// const envVars = ParseEnvVars('SINGLE=\'\'\'\'\nSINGLE_ONE=\'\'single_one\'\'\nSINGLE_TWO="\'single_two\'"\n')
|
||||
// assert(envVars.SINGLE === '\'\'')
|
||||
// assert(envVars.SINGLE_ONE === '\'single_one\'')
|
||||
// assert(envVars.SINGLE_TWO === '\'single_two\'')
|
||||
// })
|
||||
|
||||
// it('should parse out all env vars ignoring spaces around = sign', 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 all env vars ignoring spaces around = sign', 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')
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('JSON and JS format support', function () {
|
||||
// before(function () {
|
||||
// this.readFileStub = sinon.stub(fs, 'readFileSync')
|
||||
// proxyquire.noCallThru()
|
||||
// })
|
||||
// after(function () {
|
||||
// this.readFileStub.restore()
|
||||
// proxyquire.callThru()
|
||||
// })
|
||||
// afterEach(function () {
|
||||
// spawnStub.resetHistory()
|
||||
// })
|
||||
// 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')
|
||||
// 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 JavaScript with node module loader if file extension is .js', function () {
|
||||
// EnvCmd(['./test/.env.js', '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')
|
||||
// })
|
||||
// })
|
||||
|
||||
// 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",
|
||||
// "TEST_CASES": true
|
||||
// },
|
||||
// "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.resetHistory()
|
||||
// })
|
||||
// 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')
|
||||
// })
|
||||
|
||||
// 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`))
|
||||
// }
|
||||
// })
|
||||
|
||||
// it('should parse env vars from .env-cmdrc file using both development and production env', function () {
|
||||
// EnvCmd(['development,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')
|
||||
// assert(spawnStub.args[0][2].env.TEST_CASES === true)
|
||||
// })
|
||||
|
||||
// it('should parse env vars from .env-cmdrc file using both development and production env in reverse order', function () {
|
||||
// EnvCmd(['production,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')
|
||||
// assert(spawnStub.args[0][2].env.TEST_CASES === true)
|
||||
// })
|
||||
|
||||
// it('should not fail if only one environment name exists', function () {
|
||||
// EnvCmd(['production,test', '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')
|
||||
// })
|
||||
|
||||
// it('should throw error if .rc file is not valid JSON', function () {
|
||||
// this.readFileStub.returns(`{
|
||||
// "development": {
|
||||
// "BOB": "COOL",
|
||||
// "NODE_ENV": "dev",
|
||||
// "ANSWER": "42"
|
||||
// },
|
||||
// "production": {
|
||||
// "BOB": 'COOL',
|
||||
// "NODE_ENV": "prod",
|
||||
// "ANSWER": "43"
|
||||
// }
|
||||
// }`)
|
||||
// try {
|
||||
// EnvCmd(['staging', 'echo', '$BOB'])
|
||||
// assert(!'Should throw invalid JSON error.')
|
||||
// } catch (e) {
|
||||
// assert(e.message.includes(`.env-cmdrc`))
|
||||
// assert(e.message.includes(`parse`))
|
||||
// assert(e.message.includes(`JSON`))
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('EnvCmd', function () {
|
||||
// before(function () {
|
||||
// this.readFileStub = sinon.stub(fs, 'readFileSync')
|
||||
// })
|
||||
// after(function () {
|
||||
// this.readFileStub.restore()
|
||||
// })
|
||||
// afterEach(function () {
|
||||
// spawnStub.resetHistory()
|
||||
// this.readFileStub.resetHistory()
|
||||
// process.removeAllListeners()
|
||||
// })
|
||||
// it('should spawn a new process with the env vars set', function () {
|
||||
// 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.ANSWER === '42')
|
||||
// })
|
||||
|
||||
// it('should spawn a new process without overriding shell env vars', function () {
|
||||
// process.env.NODE_ENV = 'development'
|
||||
// process.env.BOB = 'SUPERCOOL'
|
||||
// this.readFileStub.returns('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
|
||||
// EnvCmd(['--no-override', './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 === 'SUPERCOOL')
|
||||
// assert(spawnStub.args[0][2].env.NODE_ENV === 'development')
|
||||
// assert(spawnStub.args[0][2].env.ANSWER === '42')
|
||||
// })
|
||||
|
||||
// it('should throw error if file and fallback does not exist with --fallback option', function () {
|
||||
// this.readFileStub.restore()
|
||||
|
||||
// try {
|
||||
// EnvCmd(['--fallback', './test/.non-existent-file', 'echo', '$BOB'])
|
||||
// } catch (e) {
|
||||
// const resolvedPath = path.join(process.cwd(), '.env')
|
||||
// assert(e.message === `Error! Could not find fallback file or read env file at ${resolvedPath}`)
|
||||
// return
|
||||
// }
|
||||
// assert(!'No exception thrown')
|
||||
// })
|
||||
|
||||
// it('should execute successfully if no env file found', function () {
|
||||
// this.readFileStub.restore()
|
||||
// process.env.NODE_ENV = 'dev'
|
||||
// process.env.ANSWER = '42'
|
||||
// delete process.env.BOB
|
||||
|
||||
// EnvCmd(['./test/.non-existent-file', 'echo', '$BOB'])
|
||||
// assert(spawnStub.args[0][0] === 'echo')
|
||||
// assert(spawnStub.args[0][1][0] === '$BOB')
|
||||
// assert(spawnStub.args[0][2].env.BOB === undefined)
|
||||
// assert(spawnStub.args[0][2].env.NODE_ENV === 'dev')
|
||||
// assert(spawnStub.args[0][2].env.ANSWER === '42')
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe('PrintHelp', function () {
|
||||
// it('should return help text when run', function () {
|
||||
// const helpText = PrintHelp()
|
||||
// assert(typeof helpText === 'string')
|
||||
// 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('ResolveEnvFilePath', function () {
|
||||
// beforeEach(function () {
|
||||
// this.cwdStub = sinon.stub(process, 'cwd')
|
||||
// this.cwdStub.returns('/Users/hitchhikers-guide-to-the-galaxy/Thanks')
|
||||
// })
|
||||
// afterEach(function () {
|
||||
// this.cwdStub.restore()
|
||||
// })
|
||||
// it('should add "fish.env" to the end of the current directory', function () {
|
||||
// const abPath = ResolveEnvFilePath('fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/fish.env')
|
||||
// })
|
||||
// it('should add "./fish.env" to the end of the current directory', function () {
|
||||
// const abPath = ResolveEnvFilePath('./fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/fish.env')
|
||||
// })
|
||||
// it('should add "../fish.env" to the end of the current directory', function () {
|
||||
// const abPath = ResolveEnvFilePath('../fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/fish.env')
|
||||
// })
|
||||
// it('should add "for-all-the/fish.env" to the end of the current directory', function () {
|
||||
// const abPath = ResolveEnvFilePath('for-all-the/fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/for-all-the/fish.env')
|
||||
// })
|
||||
// it('should set the absolute path to "/thanks/for-all-the/fish.env"', function () {
|
||||
// const abPath = ResolveEnvFilePath('/thanks/for-all-the/fish.env')
|
||||
// assert(abPath === '/thanks/for-all-the/fish.env')
|
||||
// })
|
||||
// it('should use "~" to add "fish.env" to the end of user directory', function () {
|
||||
// const abPath = ResolveEnvFilePath('~/fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/fish.env')
|
||||
// })
|
||||
// it('should leave "~" in path if no user home directory found', function () {
|
||||
// userHomeDir = ''
|
||||
// const abPath = ResolveEnvFilePath('~/fish.env')
|
||||
// assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/~/fish.env')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
49
test/parse-args.ts
Normal file
49
test/parse-args.ts
Normal file
@ -0,0 +1,49 @@
|
||||
// import * as assert from 'better-assert'
|
||||
// import * as path from 'path'
|
||||
// import * as proxyquire from 'proxyquire'
|
||||
// import * as sinon from 'sinon'
|
||||
// import * as fs from 'fs'
|
||||
// import { parseArgs } from '../src/parse-args'
|
||||
|
||||
// let userHomeDir = '/Users/hitchhikers-guide-to-the-galaxy'
|
||||
// const spawnStub = sinon.spy(() => ({
|
||||
// on: sinon.stub(),
|
||||
// exit: sinon.stub(),
|
||||
// kill: sinon.stub()
|
||||
// }))
|
||||
|
||||
// describe('env-cmd', function () {
|
||||
// describe('parseArgs', function () {
|
||||
// it('should parse out --no-override option ', function () {
|
||||
// const parsedArgs = parseArgs(['--no-override', './test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.options.noOverride === true)
|
||||
// })
|
||||
|
||||
// it('should parse out the envfile', function () {
|
||||
// const parsedArgs = parseArgs(['-f', './test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.envValues === './test/envFile')
|
||||
// })
|
||||
|
||||
// it('should parse out the command', function () {
|
||||
// const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.command === 'command')
|
||||
// })
|
||||
|
||||
// it('should parse out the command args', function () {
|
||||
// const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
// assert(parsedArgs.commandArgs.length === 2)
|
||||
// assert(parsedArgs.commandArgs[0] === 'cmda1')
|
||||
// assert(parsedArgs.commandArgs[1] === 'cmda2')
|
||||
// })
|
||||
|
||||
// it('should error out if incorrect number of args passed', function () {
|
||||
// try {
|
||||
// ParseArgs(['./test/envFile'])
|
||||
// } catch (e) {
|
||||
// assert(e.message === 'Error! Too few arguments passed to env-cmd.')
|
||||
// return
|
||||
// }
|
||||
// assert(!'No exception thrown')
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
128
test/signal-termination.ts
Normal file
128
test/signal-termination.ts
Normal file
@ -0,0 +1,128 @@
|
||||
|
||||
import * as assert from 'better-assert'
|
||||
import * as sinon from 'sinon'
|
||||
import {
|
||||
handleUncaughtExceptions, terminateSpawnedProc, terminateParentProcess
|
||||
} from '../src/signal-termination'
|
||||
|
||||
describe('signal-termination', () => {
|
||||
describe('handleUncaughtExceptions', function () {
|
||||
beforeEach(function () {
|
||||
this.logStub = sinon.stub(console, 'log')
|
||||
this.processStub = sinon.stub(process, 'exit')
|
||||
})
|
||||
afterEach(function () {
|
||||
this.logStub.restore()
|
||||
this.processStub.restore()
|
||||
})
|
||||
it('should print help text and error if error contains \'passed\'', function () {
|
||||
handleUncaughtExceptions(new Error('print help text passed now'))
|
||||
assert(this.logStub.calledTwice)
|
||||
this.logStub.restore() // restore here so test success logs get printed
|
||||
})
|
||||
|
||||
it('should print just there error if error does not contain \'passed\'', function () {
|
||||
handleUncaughtExceptions(new Error('do not print help text now'))
|
||||
assert(this.logStub.calledOnce)
|
||||
this.logStub.restore() // restore here so test success logs get printed
|
||||
})
|
||||
})
|
||||
|
||||
describe('terminateSpawnedProc', function () {
|
||||
beforeEach(function () {
|
||||
this.procStub = sinon.stub()
|
||||
this.exitStub = sinon.stub(process, 'exit')
|
||||
this.killStub = sinon.stub(process, 'kill')
|
||||
this.proc = {
|
||||
kill: this.procStub
|
||||
}
|
||||
this.exitCalled = false
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.exitStub.restore()
|
||||
this.killStub.restore()
|
||||
})
|
||||
|
||||
it('should call kill method on spawned process', function () {
|
||||
terminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call kill method more than once', function () {
|
||||
terminateSpawnedProc.call(this, this.proc)
|
||||
terminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call kill method if the spawn process is already dying', function () {
|
||||
this.exitCalled = true
|
||||
terminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 0)
|
||||
})
|
||||
|
||||
it('should call kill method on spawned process with correct signal', function () {
|
||||
terminateSpawnedProc.call(this, this.proc, 'SIGINT')
|
||||
assert(this.procStub.callCount === 1)
|
||||
assert(this.procStub.args[0][0] === 'SIGINT')
|
||||
})
|
||||
|
||||
it('should call kill method on parent process with correct signal', function () {
|
||||
terminateSpawnedProc.call(this, this.proc, 'SIGINT')
|
||||
assert(this.exitStub.callCount === 0)
|
||||
assert(this.killStub.callCount === 1)
|
||||
assert(this.killStub.args[0][1] === 'SIGINT')
|
||||
})
|
||||
|
||||
it('should call exit method on parent process with correct exit code', function () {
|
||||
terminateSpawnedProc.call(this, this.proc, 'SIGINT', 1)
|
||||
assert(this.killStub.callCount === 0)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
assert(this.exitStub.args[0][0] === 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('terminateParentProcess', function () {
|
||||
beforeEach(function () {
|
||||
this.exitStub = sinon.stub(process, 'exit')
|
||||
this.killStub = sinon.stub(process, 'kill')
|
||||
this.exitCalled = false
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.exitStub.restore()
|
||||
this.killStub.restore()
|
||||
})
|
||||
|
||||
it('should call exit method on parent process', function () {
|
||||
terminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call exit method more than once', function () {
|
||||
terminateParentProcess.call(this)
|
||||
terminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call exit method if the process is already dying', function () {
|
||||
this.exitCalled = true
|
||||
terminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 0)
|
||||
})
|
||||
|
||||
it('should call exit method with correct error code', function () {
|
||||
terminateParentProcess.call(this, 0)
|
||||
assert(this.killStub.callCount === 0)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
assert(this.exitStub.args[0][0] === 0)
|
||||
})
|
||||
|
||||
it('should call kill method with correct kill signal', function () {
|
||||
terminateParentProcess.call(this, null, 'SIGINT')
|
||||
assert(this.killStub.callCount === 1)
|
||||
assert(this.exitStub.callCount === 0)
|
||||
assert(this.killStub.args[0][1] === 'SIGINT')
|
||||
})
|
||||
})
|
||||
})
|
||||
548
test/test.js
548
test/test.js
@ -1,548 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const assert = require('better-assert')
|
||||
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')
|
||||
let userHomeDir = '/Users/hitchhikers-guide-to-the-galaxy'
|
||||
|
||||
const spawnStub = sinon.spy(() => ({
|
||||
on: sinon.stub(),
|
||||
exit: sinon.stub(),
|
||||
kill: sinon.stub()
|
||||
}))
|
||||
|
||||
const lib = proxyquire('../lib', {
|
||||
'cross-spawn': {
|
||||
spawn: spawnStub
|
||||
},
|
||||
[path.resolve(process.cwd(), 'test/.env.json')]: {
|
||||
BOB: 'COOL',
|
||||
NODE_ENV: 'dev',
|
||||
ANSWER: '42'
|
||||
},
|
||||
[path.resolve(process.cwd(), 'test/.env.js')]: {
|
||||
BOB: 'COOL',
|
||||
NODE_ENV: 'dev',
|
||||
ANSWER: '42'
|
||||
},
|
||||
'os': {
|
||||
homedir: () => userHomeDir
|
||||
}
|
||||
})
|
||||
const EnvCmd = lib.EnvCmd
|
||||
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
|
||||
const ResolveEnvFilePath = lib.ResolveEnvFilePath
|
||||
const TerminateSpawnedProc = lib.TerminateSpawnedProc
|
||||
const TerminateParentProcess = lib.TerminateParentProcess
|
||||
|
||||
describe('env-cmd', function () {
|
||||
describe('ParseArgs', function () {
|
||||
it('should parse out --no-override option ', function () {
|
||||
const parsedArgs = ParseArgs(['--no-override', './test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
assert(parsedArgs.noOverride === true)
|
||||
})
|
||||
|
||||
it('should parse out the envfile', function () {
|
||||
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
assert(parsedArgs.envFile === './test/envFile')
|
||||
})
|
||||
|
||||
it('should parse out the command', function () {
|
||||
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
assert(parsedArgs.command === 'command')
|
||||
})
|
||||
|
||||
it('should parse out the command args', function () {
|
||||
const parsedArgs = ParseArgs(['./test/envFile', 'command', 'cmda1', 'cmda2'])
|
||||
assert(parsedArgs.commandArgs.length === 2)
|
||||
assert(parsedArgs.commandArgs[0] === 'cmda1')
|
||||
assert(parsedArgs.commandArgs[1] === 'cmda2')
|
||||
})
|
||||
|
||||
it('should error out if incorrect number of args passed', function () {
|
||||
try {
|
||||
ParseArgs(['./test/envFile'])
|
||||
} catch (e) {
|
||||
assert(e.message === 'Error! Too few arguments passed to env-cmd.')
|
||||
return
|
||||
}
|
||||
assert(!'No exception thrown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('ParseEnvString', function () {
|
||||
it('should parse env vars and merge (overwrite) with process.env vars', function () {
|
||||
const env = ParseEnvString('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
|
||||
assert(env.BOB === 'COOL')
|
||||
assert(env.NODE_ENV === 'dev')
|
||||
assert(env.ANSWER === '42')
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
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 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 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')
|
||||
})
|
||||
|
||||
it('should default an empty value to an empty string', function () {
|
||||
const envVars = ParseEnvVars('EMPTY=\n')
|
||||
assert(envVars.EMPTY === '')
|
||||
})
|
||||
|
||||
it('should escape double quoted values', function () {
|
||||
const envVars = ParseEnvVars('DOUBLE_QUOTES="double_quotes"\n')
|
||||
assert(envVars.DOUBLE_QUOTES === 'double_quotes')
|
||||
})
|
||||
|
||||
it('should escape single quoted values', function () {
|
||||
const envVars = ParseEnvVars('SINGLE_QUOTES=\'single_quotes\'\n')
|
||||
assert(envVars.SINGLE_QUOTES === 'single_quotes')
|
||||
})
|
||||
|
||||
it('should preserve embedded double quotes', function () {
|
||||
const envVars = ParseEnvVars('DOUBLE=""""\nDOUBLE_ONE=\'"double_one"\'\nDOUBLE_TWO=""double_two""\n')
|
||||
assert(envVars.DOUBLE === '""')
|
||||
assert(envVars.DOUBLE_ONE === '"double_one"')
|
||||
assert(envVars.DOUBLE_TWO === '"double_two"')
|
||||
})
|
||||
|
||||
it('should preserve embedded single quotes', function () {
|
||||
const envVars = ParseEnvVars('SINGLE=\'\'\'\'\nSINGLE_ONE=\'\'single_one\'\'\nSINGLE_TWO="\'single_two\'"\n')
|
||||
assert(envVars.SINGLE === '\'\'')
|
||||
assert(envVars.SINGLE_ONE === '\'single_one\'')
|
||||
assert(envVars.SINGLE_TWO === '\'single_two\'')
|
||||
})
|
||||
|
||||
it('should parse out all env vars ignoring spaces around = sign', 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 all env vars ignoring spaces around = sign', 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')
|
||||
})
|
||||
})
|
||||
|
||||
describe('JSON and JS format support', function () {
|
||||
before(function () {
|
||||
this.readFileStub = sinon.stub(fs, 'readFileSync')
|
||||
proxyquire.noCallThru()
|
||||
})
|
||||
after(function () {
|
||||
this.readFileStub.restore()
|
||||
proxyquire.callThru()
|
||||
})
|
||||
afterEach(function () {
|
||||
spawnStub.resetHistory()
|
||||
})
|
||||
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')
|
||||
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 JavaScript with node module loader if file extension is .js', function () {
|
||||
EnvCmd(['./test/.env.js', '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')
|
||||
})
|
||||
})
|
||||
|
||||
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",
|
||||
"TEST_CASES": true
|
||||
},
|
||||
"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.resetHistory()
|
||||
})
|
||||
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')
|
||||
})
|
||||
|
||||
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`))
|
||||
}
|
||||
})
|
||||
|
||||
it('should parse env vars from .env-cmdrc file using both development and production env', function () {
|
||||
EnvCmd(['development,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')
|
||||
assert(spawnStub.args[0][2].env.TEST_CASES === true)
|
||||
})
|
||||
|
||||
it('should parse env vars from .env-cmdrc file using both development and production env in reverse order', function () {
|
||||
EnvCmd(['production,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')
|
||||
assert(spawnStub.args[0][2].env.TEST_CASES === true)
|
||||
})
|
||||
|
||||
it('should not fail if only one environment name exists', function () {
|
||||
EnvCmd(['production,test', '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')
|
||||
})
|
||||
|
||||
it('should throw error if .rc file is not valid JSON', function () {
|
||||
this.readFileStub.returns(`{
|
||||
"development": {
|
||||
"BOB": "COOL",
|
||||
"NODE_ENV": "dev",
|
||||
"ANSWER": "42"
|
||||
},
|
||||
"production": {
|
||||
"BOB": 'COOL',
|
||||
"NODE_ENV": "prod",
|
||||
"ANSWER": "43"
|
||||
}
|
||||
}`)
|
||||
try {
|
||||
EnvCmd(['staging', 'echo', '$BOB'])
|
||||
assert(!'Should throw invalid JSON error.')
|
||||
} catch (e) {
|
||||
assert(e.message.includes(`.env-cmdrc`))
|
||||
assert(e.message.includes(`parse`))
|
||||
assert(e.message.includes(`JSON`))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('EnvCmd', function () {
|
||||
before(function () {
|
||||
this.readFileStub = sinon.stub(fs, 'readFileSync')
|
||||
})
|
||||
after(function () {
|
||||
this.readFileStub.restore()
|
||||
})
|
||||
afterEach(function () {
|
||||
spawnStub.resetHistory()
|
||||
this.readFileStub.resetHistory()
|
||||
process.removeAllListeners()
|
||||
})
|
||||
it('should spawn a new process with the env vars set', function () {
|
||||
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.ANSWER === '42')
|
||||
})
|
||||
|
||||
it('should spawn a new process without overriding shell env vars', function () {
|
||||
process.env.NODE_ENV = 'development'
|
||||
process.env.BOB = 'SUPERCOOL'
|
||||
this.readFileStub.returns('BOB=COOL\nNODE_ENV=dev\nANSWER=42\n')
|
||||
EnvCmd(['--no-override', './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 === 'SUPERCOOL')
|
||||
assert(spawnStub.args[0][2].env.NODE_ENV === 'development')
|
||||
assert(spawnStub.args[0][2].env.ANSWER === '42')
|
||||
})
|
||||
|
||||
it('should throw error if file and fallback does not exist with --fallback option', function () {
|
||||
this.readFileStub.restore()
|
||||
|
||||
try {
|
||||
EnvCmd(['--fallback', './test/.non-existent-file', 'echo', '$BOB'])
|
||||
} catch (e) {
|
||||
const resolvedPath = path.join(process.cwd(), '.env')
|
||||
assert(e.message === `Error! Could not find fallback file or read env file at ${resolvedPath}`)
|
||||
return
|
||||
}
|
||||
assert(!'No exception thrown')
|
||||
})
|
||||
|
||||
it('should execute successfully if no env file found', function () {
|
||||
this.readFileStub.restore()
|
||||
process.env.NODE_ENV = 'dev'
|
||||
process.env.ANSWER = '42'
|
||||
delete process.env.BOB
|
||||
|
||||
EnvCmd(['./test/.non-existent-file', 'echo', '$BOB'])
|
||||
assert(spawnStub.args[0][0] === 'echo')
|
||||
assert(spawnStub.args[0][1][0] === '$BOB')
|
||||
assert(spawnStub.args[0][2].env.BOB === undefined)
|
||||
assert(spawnStub.args[0][2].env.NODE_ENV === 'dev')
|
||||
assert(spawnStub.args[0][2].env.ANSWER === '42')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PrintHelp', function () {
|
||||
it('should return help text when run', function () {
|
||||
const helpText = PrintHelp()
|
||||
assert(typeof helpText === 'string')
|
||||
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')
|
||||
this.processStub = sinon.stub(process, 'exit')
|
||||
})
|
||||
afterEach(function () {
|
||||
this.logStub.restore()
|
||||
this.processStub.restore()
|
||||
})
|
||||
it('should print help text and error if error contains \'passed\'', function () {
|
||||
HandleUncaughtExceptions(new Error('print help text passed now'))
|
||||
assert(this.logStub.calledTwice)
|
||||
this.logStub.restore() // restore here so test success logs get printed
|
||||
})
|
||||
|
||||
it('should print just there error if error does not contain \'passed\'', function () {
|
||||
HandleUncaughtExceptions(new Error('do not print help text now'))
|
||||
assert(this.logStub.calledOnce)
|
||||
this.logStub.restore() // restore here so test success logs get printed
|
||||
})
|
||||
})
|
||||
|
||||
describe('ResolveEnvFilePath', function () {
|
||||
beforeEach(function () {
|
||||
this.cwdStub = sinon.stub(process, 'cwd')
|
||||
this.cwdStub.returns('/Users/hitchhikers-guide-to-the-galaxy/Thanks')
|
||||
})
|
||||
afterEach(function () {
|
||||
this.cwdStub.restore()
|
||||
})
|
||||
it('should add "fish.env" to the end of the current directory', function () {
|
||||
const abPath = ResolveEnvFilePath('fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/fish.env')
|
||||
})
|
||||
it('should add "./fish.env" to the end of the current directory', function () {
|
||||
const abPath = ResolveEnvFilePath('./fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/fish.env')
|
||||
})
|
||||
it('should add "../fish.env" to the end of the current directory', function () {
|
||||
const abPath = ResolveEnvFilePath('../fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/fish.env')
|
||||
})
|
||||
it('should add "for-all-the/fish.env" to the end of the current directory', function () {
|
||||
const abPath = ResolveEnvFilePath('for-all-the/fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/for-all-the/fish.env')
|
||||
})
|
||||
it('should set the absolute path to "/thanks/for-all-the/fish.env"', function () {
|
||||
const abPath = ResolveEnvFilePath('/thanks/for-all-the/fish.env')
|
||||
assert(abPath === '/thanks/for-all-the/fish.env')
|
||||
})
|
||||
it('should use "~" to add "fish.env" to the end of user directory', function () {
|
||||
const abPath = ResolveEnvFilePath('~/fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/fish.env')
|
||||
})
|
||||
it('should leave "~" in path if no user home directory found', function () {
|
||||
userHomeDir = ''
|
||||
const abPath = ResolveEnvFilePath('~/fish.env')
|
||||
assert(abPath === '/Users/hitchhikers-guide-to-the-galaxy/Thanks/~/fish.env')
|
||||
})
|
||||
})
|
||||
|
||||
describe('TerminateSpawnedProc', function () {
|
||||
beforeEach(function () {
|
||||
this.procStub = sinon.stub()
|
||||
this.exitStub = sinon.stub(process, 'exit')
|
||||
this.killStub = sinon.stub(process, 'kill')
|
||||
this.proc = {
|
||||
kill: this.procStub
|
||||
}
|
||||
this.exitCalled = false
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.exitStub.restore()
|
||||
this.killStub.restore()
|
||||
})
|
||||
|
||||
it('should call kill method on spawned process', function () {
|
||||
TerminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call kill method more than once', function () {
|
||||
TerminateSpawnedProc.call(this, this.proc)
|
||||
TerminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call kill method if the spawn process is already dying', function () {
|
||||
this.exitCalled = true
|
||||
TerminateSpawnedProc.call(this, this.proc)
|
||||
assert(this.procStub.callCount === 0)
|
||||
})
|
||||
|
||||
it('should call kill method on spawned process with correct signal', function () {
|
||||
TerminateSpawnedProc.call(this, this.proc, 'SIGINT')
|
||||
assert(this.procStub.callCount === 1)
|
||||
assert(this.procStub.args[0][0] === 'SIGINT')
|
||||
})
|
||||
|
||||
it('should call kill method on parent process with correct signal', function () {
|
||||
TerminateSpawnedProc.call(this, this.proc, 'SIGINT')
|
||||
assert(this.exitStub.callCount === 0)
|
||||
assert(this.killStub.callCount === 1)
|
||||
assert(this.killStub.args[0][1] === 'SIGINT')
|
||||
})
|
||||
|
||||
it('should call exit method on parent process with correct exit code', function () {
|
||||
TerminateSpawnedProc.call(this, this.proc, 'SIGINT', 1)
|
||||
assert(this.killStub.callCount === 0)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
assert(this.exitStub.args[0][0] === 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('TerminateParentProcess', function () {
|
||||
beforeEach(function () {
|
||||
this.exitStub = sinon.stub(process, 'exit')
|
||||
this.killStub = sinon.stub(process, 'kill')
|
||||
this.exitCalled = false
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.exitStub.restore()
|
||||
this.killStub.restore()
|
||||
})
|
||||
|
||||
it('should call exit method on parent process', function () {
|
||||
TerminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call exit method more than once', function () {
|
||||
TerminateParentProcess.call(this)
|
||||
TerminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
})
|
||||
|
||||
it('should not call exit method if the process is already dying', function () {
|
||||
this.exitCalled = true
|
||||
TerminateParentProcess.call(this)
|
||||
assert(this.exitStub.callCount === 0)
|
||||
})
|
||||
|
||||
it('should call exit method with correct error code', function () {
|
||||
TerminateParentProcess.call(this, 0)
|
||||
assert(this.killStub.callCount === 0)
|
||||
assert(this.exitStub.callCount === 1)
|
||||
assert(this.exitStub.args[0][0] === 0)
|
||||
})
|
||||
|
||||
it('should call kill method with correct kill signal', function () {
|
||||
TerminateParentProcess.call(this, null, 'SIGINT')
|
||||
assert(this.killStub.callCount === 1)
|
||||
assert(this.exitStub.callCount === 0)
|
||||
assert(this.killStub.args[0][1] === 'SIGINT')
|
||||
})
|
||||
})
|
||||
})
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib",
|
||||
"allowJs": true,
|
||||
"target": "es5",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es2017",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user