serverless/lib/utils/index.js

853 lines
27 KiB
JavaScript

'use strict';
/**
* Serverless CLI: Utilities
*/
require('shelljs/global');
let BbPromise = require('bluebird'),
rawDebug = require('debug'),
path = require('path'),
async = require('async'),
readdirp = require('readdirp'),
SError = require('../ServerlessError'),
fs = require('fs'),
mkdirpAsync = require('mkdirp-then'),
shortid = require('shortid');
BbPromise.promisifyAll(fs);
/**
* Supported Runtimes
*/
module.exports.supportedRuntimes = {
nodejs: {
defaultPkgMgr: 'npm',
validPkgMgrs: ['npm'],
},
};
/**
* Get Root Path
*/
exports.getProjectPath = function(startDir) {
let _this = this;
// Check if startDir is root
if (_this.fileExistsSync(path.join(startDir, 's-project.json'))) {
let jawsJsonInDir = require(path.join(startDir, 's-project.json'));
if (typeof jawsJsonInDir.name !== 'undefined') return path.resolve(startDir);
}
// Check up to 10 parent levels
let previous = './',
projRootPath = false;
for (let i = 0; i < 10; i++) {
previous = path.join(previous, '../');
let fullPath = path.resolve(startDir, previous);
if (_this.fileExistsSync(path.join(fullPath, 's-project.json'))) {
let jawsJson = require(path.join(fullPath, 's-project.json'));
if (typeof jawsJson.name !== 'undefined') {
projRootPath = fullPath;
break;
}
}
}
return projRootPath;
};
/**
* GetProject
* - Create a project javascript object from al JSON files
*/
exports.getProject = function(projectRootPath) {
let _this = this,
project = {};
// Get Project JSON
project = _this.readAndParseJsonSync(path.join(projectRootPath, 's-project.json'));
// Add Modules & Functions
project.modules = {};
let moduleList = fs.readdirSync(path.join(projectRootPath, 'back', 'modules'));
for (let i = 0; i < moduleList.length; i++) {
try {
let module = _this.readAndParseJsonSync(path.join(projectRootPath, 'back', 'modules', moduleList[i], 's-module.json'));
project.modules[module.name] = module;
project.modules[module.name].pathModule = path.join('back', 'modules', moduleList[i], 's-module.json');
project.modules[module.name].functions = {};
// Get Functions
let moduleFolders = fs.readdirSync(path.join(projectRootPath, 'back', 'modules', moduleList[i]));
for (let j = 0; j < moduleFolders.length; j++) {
// Check functionPath exists
if (_this.fileExistsSync(path.join(projectRootPath, 'back', 'modules', moduleList[i], moduleFolders[j], 's-function.json'))) {
let funcs = _this.readAndParseJsonSync(path.join(projectRootPath, 'back', 'modules', moduleList[i], moduleFolders[j], 's-function.json'));
for (let k = 0; k < Object.keys(funcs.functions).length; k++) {
project.modules[module.name].functions[Object.keys(funcs.functions)[k]] = funcs.functions[Object.keys(funcs.functions)[k]];
project.modules[module.name].functions[Object.keys(funcs.functions)[k]].name = Object.keys(funcs.functions)[k];
project.modules[module.name].functions[Object.keys(funcs.functions)[k]].pathFunction = path.join('back', 'modules', moduleList[i], moduleFolders[j], 's-function.json');
}
}
}
} catch (e) {
console.log(e);
}
}
return project;
};
/**
* Get Meta
* - Get Project Meta Information
*/
exports.getMeta = function(projectRootPath) {
let _this = this,
projectMeta = {
private: {
stages: {},
variables: {}
},
public: {
variables: {}
}
};
// Re-usable function to traverse public or private variable folders
let _getVariables = function(type) {
let variableFiles = fs.readdirSync(path.join(projectRootPath, 'meta', type, 'variables'));
for (let i = 0; i < variableFiles.length; i++) {
let variableFile = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
// Parse file name to get stage/region
let file = variableFiles[i].replace('s-variables-', '').replace('.json', '');
if (file === 'common') {
// Set Common variables
projectMeta[type].variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
} else {
// Set Stage/Region variables
file = file.split('-');
if (!projectMeta[type].stages) projectMeta[type].stages = {};
if (!projectMeta[type].stages[file[0]]) projectMeta[type].stages[file[0]] = {
regions: {},
variables: {}
};
if (file.length === 1) {
// Set Stage Variables
projectMeta[type].stages[file[0]].variables = _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]));
} else if (file.length === 2) {
// Set Stage-Region Variables
let region;
if (file[1] === 'useast1') region = 'us-east-1';
if (file[1] === 'uswest2') region = 'us-west-2';
if (file[1] === 'euwest1') region = 'eu-west-1';
if (file[1] === 'apnortheast1') region = 'ap-northeast-1';
if (!projectMeta[type].stages[file[0]].regions[region]) projectMeta[type].stages[file[0]].regions[region] = {
variables: _this.readAndParseJsonSync(path.join(projectRootPath, 'meta', type, 'variables', variableFiles[i]))
};
}
}
}
};
if (_this.dirExistsSync(path.join(projectRootPath, 'meta', 'public'))) _getVariables('public');
if (_this.dirExistsSync(path.join(projectRootPath, 'meta', 'private'))) _getVariables('private');
return projectMeta;
};
/**
* Save Meta
*/
exports.saveMeta = function(projectRootPath, projectMeta) {
// Re-usable function to save public or private variables
let _saveVariables = function(type) {
// Save Common Variables
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-common.json'),
JSON.stringify(projectMeta[type].variables, null, 2));
for (let i = 0; i < Object.keys(projectMeta[type].stages).length; i++) {
let stage = projectMeta[type].stages[Object.keys(projectMeta[type].stages)[i]];
// Save Stage Variables
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-' + Object.keys(projectMeta[type].stages)[i] + '.json'),
JSON.stringify(stage.variables, null, 2));
// Save Stage Region Variables
for (let j = 0; j < Object.keys(stage.regions).length; j++) {
fs.writeFileSync(path.join(projectRootPath, 'meta', type, 'variables', 's-variables-' + Object.keys(projectMeta[type].stages)[i] + '-' + Object.keys(stage.regions)[j].replace(/-/g, '') + '.json'),
JSON.stringify(stage.regions[Object.keys(stage.regions)[j]].variables, null, 2));
}
}
};
if (this.dirExistsSync(path.join(projectRootPath, 'meta', 'public'))) _saveVariables('public');
if (this.dirExistsSync(path.join(projectRootPath, 'meta', 'private'))) _saveVariables('private');
};
/**
* Execute (Command)
*/
exports.execute = function(promise) {
promise
.catch(SError, function(e) {
throw e;
process.exit(e.messageId);
})
.error(function(e) {
console.error(e);
process.exit(1);
})
.done();
};
/**
* Read Recursively
*/
exports.readRecursively = function(path, filter) {
return new BbPromise(function(resolve, reject) {
let files = [];
readdirp({
root: path,
fileFilter: filter,
})
.on('data', function(entry) {
files.push(entry.path);
})
.on('error', function(error) {
reject(error);
})
.on('end', function() {
resolve(files);
});
});
};
/**
* Get Modules
* - Find all s-module json paths underneath given dir
*/
exports.getModules = function(baseDir) {
return this.readRecursively(baseDir, '*s-module.json')
.then(function(jsonPaths) {
return jsonPaths.map(function(jsonPath) {
return path.resolve(path.join(baseDir, jsonPath));
});
});
};
/**
* Return Partial
*/
exports.returnPartial = function(string, symbol, number, defaultResponse) {
if (string.indexOf(symbol) > -1) return string.split(symbol)[number];
else return defaultResponse;
};
/**
* Get Functions
* - Resolve paths and return function JSONs
* - If no paths specified, finds all functions in baseDir
* - Each function must contain # in path: module/function#get
*/
exports.getFunctions = function(baseDir, functionPaths) {
let _this = this,
allFunctionJsons = [];
return BbPromise.try(function () {
// Sanitize baseDir
if ((baseDir).indexOf('back') == -1) baseDir = path.join(baseDir, 'back');
if ((baseDir).indexOf('modules') == -1) baseDir = path.join(baseDir, 'modules');
// If functionPaths, validate and return them
if (functionPaths) {
// Validate - ensure functionPath contains #
for (let i = 0; i < functionPaths.length; i++) {
let path = functionPaths[i];
if (path.indexOf('#') === -1) {
throw new SError(`Function path is missing '#' : ${path}`,
SError.errorCodes.INVALID_RESOURCE_NAME);
}
// If absolute path, trim to be relative
if (path.indexOf('modules/') > -1) path = path.split('modules/')[1];
if (path.indexOf('modules\\') > -1) path = path.split('modules\\')[1];
}
return functionPaths;
}
// If no functionPaths, get all functions in project and create paths
functionPaths = [];
return _this.readRecursively(baseDir, '*s-function.json')
.then(function(functionFilePaths) {
// If inside modules, grab path prefix
let baseArray = baseDir.split(path.sep),
pathPrefix;
if (baseArray[baseArray.indexOf('modules') + 1]) {
pathPrefix = (baseArray.splice(baseArray.indexOf('modules') + 1)).join(path.sep);
}
// We've used the basDir to locate functions. Now, normalize baseDir
if (baseDir.indexOf('modules/') > -1) baseDir = path.join(baseDir.split('modules/')[0], 'modules/');
if (baseDir.indexOf('modules\\') > -1) baseDir = path.join(baseDir.split('modules\\')[0], 'modules\\'); // Windows
for (let i = 0; i < functionFilePaths.length; i++) {
// Read JSON
let filePath = pathPrefix ? path.join(pathPrefix, functionFilePaths[i]) : functionFilePaths[i];
let functionsObject = _this.readAndParseJsonSync(path.join(baseDir, filePath));
functionsObject = functionsObject.functions;
// Create paths for each function in s-function.json
for (let j = 0; j < Object.keys(functionsObject).length; j++ ) {
functionPaths.push(filePath + '#' + Object.keys(functionsObject)[j]);
}
}
return functionPaths;
});
})
.then(function(paths) {
// Sanitize Paths
for (let i = 0; i < paths.length; i++) {
paths[i] = paths[i].replace('s-function.json', '');
paths[i] = paths[i].replace('/#', '#');
paths[i] = paths[i].replace('\\#', '#');
// Remove slashes after functionPath
if (['/', '\\'].indexOf(paths[i].charAt(0)) !== -1) paths[i] = paths[i].substring(1, paths[i].length);
}
return paths;
})
.then(function(functionPaths) {
return new BbPromise(function(resolve, reject){
// Loop through function paths and process
async.eachLimit(functionPaths, 10, function (functionPath, cb) {
// Strip functionPath, functionKey & endpointKeys
let functionKey = _this.returnPartial(functionPath, '#', 1);
let pathFunctionRelative = _this.returnPartial(functionPath, '#', 0);
// Check functionPath exists
if (!_this.fileExistsSync(path.join(baseDir, pathFunctionRelative, 's-function.json'))) {
throw new SError(`Invalid function path ${functionPath}`, SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Get FunctionJSON
let functionsObject;
let functionJson;
try {
functionsObject = _this.readAndParseJsonSync(path.join(baseDir, pathFunctionRelative, 's-function.json'));
functionJson = functionsObject.functions[functionKey];
} catch(e) {
throw new SError(`Invalid JSON in ${functionPath}`, SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Get ModuleJSON
let moduleJson;
let modulePath = path.join(baseDir, functionPath.split(path.sep)[0], 's-module.json');
try {
moduleJson = _this.readAndParseJsonSync(modulePath);
moduleJson.pathModule = path.join('back', 'modules', pathFunctionRelative.split(path.sep)[0]);
} catch(e) {
throw new SError(
`This function has missing or invalid parent module JSON (${modulePath}) ${functionPath}`,
SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Add attributes
functionJson.name = functionKey;
functionJson.module = moduleJson;
functionJson.pathFunction = path.join('back', 'modules', pathFunctionRelative);
// Add to main array
allFunctionJsons.push(functionJson);
// Callback
return cb();
}, function (error) {
if (error) return reject(new SError(error.message));
return resolve(allFunctionJsons);
});
});
});
};
/**
* Get Endpoints
* - Return endpoint JSONs
* - If no paths specified, finds all endpoints in baseDir
* - Each endpoint must contain #, @ and ~ in path: module/function#get@get-data~GET
*/
exports.getEndpoints = function(baseDir, endpointPaths) {
let _this = this,
allEndpointJsons = [];
return BbPromise.try(function () {
// Sanitize baseDir
if ((baseDir).indexOf('/back') == -1) baseDir = path.join(baseDir, 'back');
if ((baseDir).indexOf('/modules') == -1) baseDir = path.join(baseDir, 'modules');
// If endpointPaths, validate and return them
if (endpointPaths) {
// Validate - ensure endpoint path contains #
for (let i = 0; i < endpointPaths.length; i++) {
let path = endpointPaths[i];
// Ensure pointers are included
if (path.indexOf('#') === -1 || path.indexOf('@') === -1 || path.indexOf('~') === -1) {
throw new SError(`Endpoint path is missing '#', '@' or '~' : ${path}`,
SError.errorCodes.INVALID_RESOURCE_NAME);
}
// If absolute path, trim to be relative
if (path.indexOf('/back/modules') === -1) {
path = path.split('back/modules/')[1];
}
}
return endpointPaths;
}
// If no endpointPaths, get all functions in project and create their endpoint paths
endpointPaths = [];
return _this.readRecursively(baseDir, '*s-function.json')
.then(function(functionFilePaths) {
// If inside modules, grab path prefix
let baseArray = baseDir.split(path.sep),
pathPrefix;
if (baseArray[baseArray.indexOf('modules') + 1]) {
pathPrefix = (baseArray.splice(baseArray.indexOf('modules') + 1)).join(path.sep);
}
// We've used the basDir to locate functions. Now, normalize baseDir
if (baseDir.indexOf('modules/') > -1) baseDir = path.join(baseDir.split('modules/')[0], 'modules/');
if (baseDir.indexOf('modules\\') > -1) baseDir = path.join(baseDir.split('modules\\')[0], 'modules\\'); // Windows
for (let i = 0; i < functionFilePaths.length; i++) {
// Read JSON
let filePath = pathPrefix ? path.join(pathPrefix, functionFilePaths[i]) : functionFilePaths[i];
let functionsObject = _this.readAndParseJsonSync(path.join(baseDir, filePath));
functionsObject = functionsObject.functions;
// Create paths for each function in s-function.json
for (let j = 0; j < Object.keys(functionsObject).length; j++ ) {
let functionPath = filePath + '#' + Object.keys(functionsObject)[j];
let funcObject = functionsObject[Object.keys(functionsObject)[j]];
for (let k = 0; k < funcObject.endpoints.length; k++) {
let endpointPath = functionPath + '@' + funcObject.endpoints[k].path + '~' + funcObject.endpoints[k].method;
endpointPaths.push(endpointPath);
}
}
}
return endpointPaths;
});
})
.then(function(paths) {
// Sanitize Paths
for (let i = 0; i < paths.length; i++) {
paths[i] = paths[i].replace('s-function.json', '');
paths[i] = paths[i].replace('/#', '#');
paths[i] = paths[i].replace('\\#', '#');
// Remove slashes after functionPath
if (['/', '\\'].indexOf(paths[i].charAt(0)) !== -1) paths[i] = paths[i].substring(1, paths[i].length);
}
return paths;
})
.then(function(endpointPaths) {
return new BbPromise(function(resolve, reject){
// Loop through function paths and process
async.eachLimit(endpointPaths, 10, function (pathEndpoint, cb) {
let pathFunctionRelative = null,
nameFunction = null,
endpointUrlPath = null,
endpointMethod = null;
// Get Function Properties
pathFunctionRelative = _this.returnPartial(pathEndpoint, '@', 0, null);
nameFunction = _this.returnPartial(pathFunctionRelative, '#', 1, null);
pathFunctionRelative = _this.returnPartial(pathFunctionRelative, '#', 0, null);
// Get Endpoint Properties
endpointUrlPath = _this.returnPartial(pathEndpoint, '@', 1, null);
endpointMethod = _this.returnPartial(pathEndpoint, '~', 1, null);
endpointUrlPath = _this.returnPartial(endpointUrlPath, '~', 0, null);
// If endpointPath has s-function.json missing, add it
pathEndpoint = pathEndpoint.replace('s-function.json', '');
// Check function exists
if (!_this.fileExistsSync(path.join(baseDir, pathFunctionRelative, 's-function.json'))) {
throw new SError(
`Invalid endpoint path ${pathEndpoint}`,
SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Get FunctionJSON
let functionsObject;
let endpointJson = {};
try {
functionsObject = _this.readAndParseJsonSync(path.join(baseDir, pathFunctionRelative, 's-function.json'));
let func = functionsObject.functions[nameFunction];
for (let i = 0; i < func.endpoints.length; i++) {
let endpoint = func.endpoints[i];
if (endpoint.path === endpointUrlPath && endpoint.method === endpointMethod) {
endpointJson = endpoint;
endpointJson.function = func;
endpointJson.function.name = nameFunction;
endpointJson.function.pathFunction = path.join('back', 'modules', pathFunctionRelative);
}
}
} catch(e) {
console.log(e);
throw new SError(`Invalid JSON in ${endpointUrlPath}`, SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Get ModuleJSON
let pathModule = path.join(baseDir, pathFunctionRelative.split(path.sep)[0], 's-module.json');
let moduleJson;
try {
moduleJson = _this.readAndParseJsonSync(pathModule);
moduleJson.pathModule = path.join('back', 'modules', pathFunctionRelative.split(path.sep)[0]);
} catch(e) {
throw new SError(`This endpoint has missing or invalid parent module JSON (s-module.json) ${endpointUrlPath} - ${pathModule}`,
SError.errorCodes.INVALID_RESOURCE_NAME);
}
// Add attributes
endpointJson.module = moduleJson;
// Add to main array
allEndpointJsons.push(endpointJson);
// Callback
return cb();
}, function () {
return resolve(allEndpointJsons);
});
});
});
};
/**
* getFunctionEnvVars
* - Finds all Env Vars that are used by all Functions in a given Module
*/
exports.getFunctionEnvVars = function(projectRootPath, modName) {
return this.getFunctions(path.join(projectRootPath, 'back', 'modules', modName), null)
.then(function(functionJsons) {
let envVars = [];
functionJsons.forEach(function(functionJson) {
if (functionJson.lambda && functionJson.lambda.envVar) {
functionJson.lambda.envVar.forEach(function(envVar) {
if (envVars.indexOf(envVar) == -1) {
envVars.push(envVar);
}
});
}
});
return envVars;
});
};
/**
* Write File
* - Writes file and makes any parent dirs if necessary
* @param filePath
* @param contents node Buffer
* @returns {Promise}
*/
exports.writeFile = function(filePath, contents) {
this.sDebug('Writing file:', filePath);
if (contents === undefined) {
contents = '';
}
return mkdirpAsync(path.dirname(filePath))
.then(function() {
return fs.writeFileAsync(filePath, contents);
});
};
exports.generateShortId = function(maxLen) {
return shortid.generate().replace(/\W+/g, '').substring(0, maxLen).replace(/[_-]/g, '');
};
/**
* Generate JawsBucket Name
*/
exports.generateProjectBucketName = function(region, projectDomain) {
// Sanitize
region = region.trim().replace(/-/g, '').toLowerCase();
projectDomain = projectDomain.trim().toLowerCase();
return `serverless.${region}.${projectDomain}`;
};
/**
* Given list of project stage objects, extract given region
*/
exports.getRegionConfig = function(projectMeta, stage, regionName) {
if (!projectMeta.project.stages[stage].regions[region]) {
throw new SError(`Could not find region ${regionName}`, SError.errorCodes.UNKNOWN);
}
return projectMeta.project.stages[stage].regions[region];
};
exports.dirExistsSync = function(path) {
try {
let stats = fs.statSync(path);
return stats.isDirectory();
}
catch (e) {
return false;
}
};
exports.fileExistsSync = function(path) {
try {
let stats = fs.lstatSync(path);
return stats.isFile();
}
catch (e) {
return false;
}
};
exports.readAndParseJsonSync = function(path) {
return JSON.parse(fs.readFileSync(path));
};
exports.endsWith = function(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
};
/**
* NPM Install
* - Programatically install NPM dependencies
*/
exports.npmInstall = function(dir) {
process.chdir(dir);
if (exec('npm install ', { silent: false }).code !== 0) {
throw new SError(`Error executing NPM install on ${dir}`, SError.errorCodes.UNKNOWN);
}
process.chdir(process.cwd());
};
/**
* Generate Resources CloudFormation Template
*/
exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage, region, notificationEmail) {
let cfTemplate = require('../templates/resources-cf');
cfTemplate.Parameters.ProjectName.Default = projName;
cfTemplate.Parameters.ProjectName.AllowedValues = [projName];
cfTemplate.Parameters.ProjectDomain.Default = projDomain;
cfTemplate.Parameters.Stage.AllowedValues = [stage];
cfTemplate.Parameters.DataModelStage.AllowedValues = [stage];
cfTemplate.Parameters.NotificationEmail.Default = notificationEmail;
cfTemplate.Description = projName + ' resources';
return this.writeFile(
path.join(projRootPath, 'meta', 'private', 'resources', 's-resources-cf.json'),
JSON.stringify(cfTemplate, null, 2));
};
/**
* Write to console.log if process.env.DEBUG is true
* - If we ever want to get more complicated with log levels we should use winston
*/
let debuggerCache = {};
exports.sDebugWithContext = function(context) {
if (process.env.DEBUG) {
context = `serverless:${context}`;
if (!debuggerCache[context]) {
debuggerCache[context] = rawDebug(context);
}
debuggerCache[context].apply(null, Array.prototype.slice.call(arguments, 1));
}
};
exports.sDebug = function() {
if (process.env.DEBUG) {
let caller = getCaller();
let context = pathToContext(caller);
let args = Array.prototype.slice.call(arguments);
args.unshift(context);
this.sDebugWithContext.apply(this, args);
}
};
exports.isStageNameValid = function(stageName) {
return /^[a-zA-Z\d]+$/.test(stageName);
};
/**
* Find Regional API
* - Finds a project REST API ID that already exists
*/
exports.findRegionalApi = function(projectJawsJson, regionName) {
for (let stages of Object.keys(projectJawsJson.stage)) {
for (let i = 0; i < stages.length; i++) {
if (stages[i].region === regionName && stages[i].restApiId) {
return stages[i].restApiId;
}
}
}
};
/**
* Save Regional API
* - Saves regional API to all stage that have this region
*/
exports.saveRegionalApi = function(projectJawsJson, regionName, restApiId, rootPath) {
for (let stages of Object.keys(projectJawsJson.stages)) {
for (let i = 0; i < projectJawsJson.stages[stages].length; i++) {
if (projectJawsJson.stages[stages][i].region === regionName) {
projectJawsJson.stages[stages][i].restApiId = restApiId;
}
}
}
fs.writeFileSync(path.join(rootPath, 's-project.json'), JSON.stringify(projectJawsJson, null, 2));
};
function pathToContext(path) {
// Match files under lib, tests, or bin so we only report the
// relevant part of the file name as the context
let pathRegex = /\/((lib|tests|bin)\/.*?)\.js$/i;
let match = pathRegex.exec(path);
if (match.length >= 2) {
return match[1].replace(/[\/\\]/g, '.');
} else {
return path;
}
}
function getCaller() {
let stack = getStack();
// Remove unwanted function calls on stack -- ourselves and our caller
stack.shift();
stack.shift();
// Now the top of the stack is the CallSite we want
// See this for available methods:
// https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
let path = stack[0].getFileName();
return path;
}
function getStack() {
// Save original Error.prepareStackTrace
let origPrepareStackTrace = Error.prepareStackTrace;
// Override with function that just returns `stack`
Error.prepareStackTrace = function(_, stack) {
return stack;
};
let err = new Error();
// Get `err.stack`, which calls our new `Error.prepareStackTrace`
let stack = err.stack;
// Restore original `Error.prepareStackTrace`
Error.prepareStackTrace = origPrepareStackTrace;
// Remove ourselves from the stack
stack.shift();
return stack;
}