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
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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'
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const spawn = require('cross-spawn').spawn
|
var spawn = require("cross-spawn");
|
||||||
const path = require('path')
|
var signal_termination_1 = require("./signal-termination");
|
||||||
const fs = require('fs')
|
var parse_args_1 = require("./parse-args");
|
||||||
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
|
* 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) {
|
function EnvCmd(args) {
|
||||||
// First Parse the args from the command line
|
// First Parse the args from the command line
|
||||||
const parsedArgs = ParseArgs(args)
|
var parsedArgs = parse_args_1.parseArgs(args);
|
||||||
|
var env;
|
||||||
// If a .rc file was found then use that
|
// Override the merge order if --no-override flag set
|
||||||
let parsedEnv
|
if (parsedArgs.options.noOverride) {
|
||||||
if (fs.existsSync(rcFileLocation)) {
|
env = Object.assign({}, parsedArgs.envValues, process.env);
|
||||||
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') {
|
else {
|
||||||
noOverride = true
|
// Add in the system environment variables to our environment list
|
||||||
continue
|
env = Object.assign({}, process.env, parsedArgs.envValues);
|
||||||
}
|
}
|
||||||
// assume the first arg is the env file (or if using .rc the environment name)
|
// Execute the command with the given environment variables
|
||||||
if (!envFile) {
|
var proc = spawn(parsedArgs.command, parsedArgs.commandArgs, {
|
||||||
envFile = arg
|
stdio: 'inherit',
|
||||||
} else {
|
env: env
|
||||||
command = arg
|
});
|
||||||
break
|
// Handle any termination signals for parent and child proceses
|
||||||
}
|
signal_termination_1.handleTermSignals(proc);
|
||||||
}
|
return env;
|
||||||
|
|
||||||
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 = {
|
module.exports = {
|
||||||
EnvCmd,
|
EnvCmd: EnvCmd
|
||||||
ParseArgs,
|
};
|
||||||
ParseEnvString,
|
|
||||||
PrintHelp,
|
|
||||||
HandleUncaughtExceptions,
|
|
||||||
TerminateSpawnedProc,
|
|
||||||
TerminateParentProcess,
|
|
||||||
StripComments,
|
|
||||||
StripEmptyLines,
|
|
||||||
ParseEnvVars,
|
|
||||||
ParseRCFile,
|
|
||||||
UseRCFile,
|
|
||||||
UseCmdLine,
|
|
||||||
ResolveEnvFilePath
|
|
||||||
}
|
|
||||||
|
|||||||
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"
|
"env-cmd": "bin/env-cmd.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha",
|
"test": "mocha -r ts-node/register ./**/*.ts",
|
||||||
"test-cover": "nyc --reporter=lcov --reporter=text npm test",
|
"test-cover": "nyc --reporter=lcov --reporter=text npm test",
|
||||||
"test-lint": "standard",
|
"test-lint": "standard",
|
||||||
"coveralls": "coveralls < coverage/lcov.info",
|
"coveralls": "coveralls < coverage/lcov.info",
|
||||||
"lint": "standard --fix"
|
"lint": "standard --env mocha --fix ./**/*.ts",
|
||||||
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -41,15 +42,28 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/toddbluhm/env-cmd#readme",
|
"homepage": "https://github.com/toddbluhm/env-cmd#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"commander": "^2.19.0",
|
||||||
"cross-spawn": "^6.0.5"
|
"cross-spawn": "^6.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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",
|
"better-assert": "^1.0.2",
|
||||||
"coveralls": "^3.0.1",
|
"coveralls": "^3.0.1",
|
||||||
"mocha": "^5.1.1",
|
"mocha": "^5.1.1",
|
||||||
"nyc": "^13.1.0",
|
"nyc": "^13.1.0",
|
||||||
"proxyquire": "^2.0.1",
|
"proxyquire": "^2.0.1",
|
||||||
"sinon": "^7.0.0",
|
"sinon": "^7.2.3",
|
||||||
"standard": "^12.0.1"
|
"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