liveperson-functions-cli
Version:
LivePerson Functions CLI
314 lines (308 loc) • 13.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FaasDebugger = void 0;
/* eslint-disable no-console */
const fs_1 = require("fs");
const child_process_1 = require("child_process");
const path_1 = require("path");
const perf_hooks_1 = require("perf_hooks");
const EXECUTION_EXCEED_TIMEOUT = 60000;
const EXTERNAL_PACKAGE_MAPPING = [
'oauth-1.0a',
'luxon',
'jsforce',
'jsonwebtoken',
'lodash',
];
function isLogLevel(input) {
return Object.keys({
Debug: 'Debug',
Info: 'Info',
Warn: 'Warn',
Error: 'Error',
Callback: 'Callback',
History: 'History',
}).some((e) => input.includes(`[${e}]`));
}
function didExecutionExceedTimewindow(timeStart, timeEnd) {
return timeEnd - timeStart > EXECUTION_EXCEED_TIMEOUT;
}
function didExecutionFailWithError(result) {
return (result.filter((e) => { var _a; return e.level === 'Error' && ((_a = e.message) === null || _a === void 0 ? void 0 : _a.errorMsg); })
.length > 0);
}
function throwInvalidProjectFolderError() {
console.log('Could not find index.js. Please make sure you have set up a functions folder with index.js and config.json');
}
function didIncorrectErrorFormat(result) {
return result.some((e) => {
var _a, _b;
return ((_a = e.extras[0]) === null || _a === void 0 ? void 0 : _a.originalFailure) &&
((_b = e.message) === null || _b === void 0 ? void 0 : _b.errorMsg.includes('incorrect format')) &&
e.level === 'Warn';
});
}
function mapExternalPackagesToToolbelt(file) {
const isReverse = EXTERNAL_PACKAGE_MAPPING.some((pkg) => file.includes(`require('../bin/lp-faas-toolbelt/${pkg}')`));
const needsMapping = isReverse ||
EXTERNAL_PACKAGE_MAPPING.some((pkg) => file.includes(`require('${pkg}')`));
if (needsMapping) {
EXTERNAL_PACKAGE_MAPPING.forEach((pkg) => {
/* istanbul ignore next */
file = isReverse
? file.replace(`require('../bin/lp-faas-toolbelt/${pkg}')`, `require('${pkg}')`)
: file.replace(`require('${pkg}')`, `require('../bin/lp-faas-toolbelt/${pkg}')`);
});
}
return file;
}
class FaasDebugger {
constructor(
/* istanbul ignore next */ { indexPath = '', configPath = '', lambdaToInvoke = '', cwd = process.cwd(), } = {}) {
this.indexPath = indexPath;
this.configPath = configPath;
this.result = {
result: {},
logs: [],
};
this.errorLogs = {
errorCode: '',
errorMsg: '',
errorLogs: [],
};
this.cwd = cwd;
this.lambdaToInvoke = lambdaToInvoke;
this.functionPath = (0, path_1.join)(cwd, 'functions', process.env.DEBUG_FUNCTION || process.argv[2]);
this.port = process.env.DEBUG_PORT
? Number.parseInt(process.env.DEBUG_PORT, 10)
: null;
}
async runLocalInvocation() {
try {
this.updateLambdaFunctionForInvoke();
this.setEnvironmentVariables(true);
await this.createChildProcessForInvokeLocal();
}
catch {
throwInvalidProjectFolderError();
}
}
async runDebugging() {
try {
this.updateLambdaFunctionForDebugging();
this.setEnvironmentVariables();
await this.createChildProcessForDebugging();
}
catch {
throwInvalidProjectFolderError();
}
}
createChildProcessForInvokeLocal() {
return new Promise((resolve) => {
const childFork = (0, child_process_1.fork)(this.indexPath, [], {
env: process.env,
detached: true,
});
const timeStart = perf_hooks_1.performance.now();
childFork.on('message', (result) => {
const timeEnd = perf_hooks_1.performance.now();
if (didExecutionExceedTimewindow(timeStart, timeEnd)) {
this.errorLogs = {
errorCode: 'com.liveperson.faas.handler.custom-failure',
errorMsg: 'Lambda did not call callback within execution time limit',
errorLogs: result,
};
console.log(JSON.stringify(this.errorLogs, null, 4));
return;
}
if (didIncorrectErrorFormat(result)) {
const error = result.filter((e) => {
var _a, _b;
return ((_a = e.extras[0]) === null || _a === void 0 ? void 0 : _a.originalFailure) &&
((_b = e.message) === null || _b === void 0 ? void 0 : _b.errorMsg.includes('incorrect format')) &&
e.level === 'Warn';
})[0];
this.errorLogs.errorCode = error.message.errorCode;
this.errorLogs.errorMsg = error.extras[0].originalFailure;
result[result.findIndex((e) => {
var _a;
return e.level === 'Warn' &&
((_a = e.message) === null || _a === void 0 ? void 0 : _a.errorMsg.includes('incorrect format'));
})].message = error.message.errorMsg;
this.errorLogs.errorLogs = result;
console.log(JSON.stringify(this.errorLogs, null, 4));
return;
}
if (didExecutionFailWithError(result)) {
const enrichedError = result.filter((e) => { var _a; return e.level === 'Error' && ((_a = e.message) === null || _a === void 0 ? void 0 : _a.errorMsg); })[0];
/* istanbul ignore else */
if (enrichedError) {
this.errorLogs.errorCode = enrichedError.message.errorCode;
this.errorLogs.errorMsg = enrichedError.message.errorMsg;
result[result.findIndex((e) => { var _a; return e.level === 'Error' && ((_a = e.message) === null || _a === void 0 ? void 0 : _a.errorMsg); })].message = `Received Error - ${enrichedError.message.errorMsg}`;
}
this.errorLogs.errorLogs = result;
console.log(JSON.stringify(this.errorLogs, null, 4));
return;
}
this.result.logs = result.filter((e) => e.level !== 'Callback');
const logs = result.filter((e) => e.level === 'Callback');
this.result.result = logs.length > 0 ? logs[0].message : '';
console.log(JSON.stringify(this.result, null, 4));
});
childFork.on('exit', () => {
this.revertLambdaFunction(true);
childFork.kill();
resolve();
});
/* istanbul ignore next */
process.on('SIGINT', () => {
console.log('Interrupted lambda invocation');
resolve();
});
/* istanbul ignore next */
process.on('SIGHUP', () => {
console.log('Interrupted lambda invocation');
resolve();
});
});
}
async createChildProcessForDebugging() {
/* istanbul ignore next */
if (!this.port) {
/* eslint-disable */
const getPort = require('./lp-faas-toolbelt/node_modules/get-port');
/* eslint-enable */
this.port = await getPort({ port: getPort.makeRange(30500, 31000) });
}
this.updatePortForFiles();
const args = [
`--inspect-brk=${this.port}`,
(0, path_1.join)(this.functionPath, 'index.js'),
];
const child = (0, child_process_1.spawn)('node', args, {
detached: false,
stdio: 'pipe',
serialization: 'advanced',
});
child.stdout.on('data', (e) => {
// eslint-disable-next-line no-console
if (isLogLevel(e.toString()))
console.log(e.toString());
});
child.stderr.on('data', (e) => {
// eslint-disable-next-line no-console
if (isLogLevel(e.toString()))
console.log(e.toString());
});
child.on('exit', () => {
this.revertLambdaFunction();
child.kill();
});
/* istanbul ignore next */
process.on('SIGINT', () => {
// eslint-disable-next-line no-console
console.log('Interrupted lambda invocation');
});
/* istanbul ignore next */
process.on('SIGHUP', () => {
// eslint-disable-next-line no-console
console.log('Interrupted lambda invocation');
});
}
setEnvironmentVariables(invoke = false) {
const { environmentVariables } = JSON.parse((0, fs_1.readFileSync)(invoke ? this.configPath : (0, path_1.join)(this.functionPath, 'config.json'), 'utf8'));
// better readable than forEach
// eslint-disable-next-line no-restricted-syntax
for (const env of environmentVariables) {
if (!Object.prototype.hasOwnProperty.call(env, 'key') ||
!Object.prototype.hasOwnProperty.call(env, 'value')) {
// eslint-disable-next-line no-console
console.log('Invalid environment variables! Please make sure to have key-value pairs as variables');
return;
}
if (env.key === '') {
return;
}
process.env[env.key] = env.value;
}
}
updateLambdaFunctionForInvoke() {
let file = (0, fs_1.readFileSync)(this.indexPath, 'utf8');
file = `require("module").prototype.require = require('../../bin/rewire').proxy; // Rewire require
${file}
// This is an auto generated code during the invocation/debugging
// It rewires the requirements and parsing the output
(async () => {
try {
console = require('../../bin/rewire').InvokeLogger;
const input = require('functions/${this.lambdaToInvoke}/config').input;
const response = await require('../../bin/rewire').convertToPromisifiedLambda((input, cb) => lambda(input, cb))(input);
console.response(response);
process.send(console.getHistory());
} catch (error) {
console.customError(error);
process.send(console.getHistory());
}
})();`;
file = mapExternalPackagesToToolbelt(file);
(0, fs_1.writeFileSync)(this.indexPath, file);
}
updateLambdaFunctionForDebugging() {
const originalCode = (0, fs_1.readFileSync)((0, path_1.join)(this.functionPath, 'index.js'), 'utf8');
let updatedCode = `require("module").prototype.require = require('../../bin/rewire').proxy; // Rewire require
${originalCode}
// This is an auto generated code during the invocation/debugging
// It rewires the requirements and parsing the output
(async () => {
try {
console = require('../../bin/rewire').DebugLogger;
const input = require('functions/${process.argv[2]}/config').input;
const response = await require('../../bin/rewire').convertToPromisifiedLambda((input, cb) => lambda(input, cb))(input);
console.response(response);
console.printHistory();
} catch (error) {
console.customError(error);
}
})();`;
updatedCode = mapExternalPackagesToToolbelt(updatedCode);
(0, fs_1.writeFileSync)((0, path_1.join)(this.functionPath, 'index.js'), updatedCode);
}
revertLambdaFunction(invoke = false) {
let updatedCode = (0, fs_1.readFileSync)(invoke ? this.indexPath : (0, path_1.join)(this.functionPath, 'index.js'), 'utf8');
updatedCode = mapExternalPackagesToToolbelt(updatedCode);
/* istanbul ignore else */
if (updatedCode.includes('This is an auto generated code')) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, originalCode1] = updatedCode.split(`// Rewire require
`);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [originalCode2] = originalCode1.split(`
// This is an auto`);
(0, fs_1.writeFileSync)(invoke ? this.indexPath : (0, path_1.join)(this.functionPath, 'index.js'), originalCode2);
}
}
updatePort(filePath) {
let content = (0, fs_1.readFileSync)(filePath, 'utf8');
const oldPort = content.match(/\d{4,5}/g);
oldPort.forEach((e) => (content = content.replace(e, `${this.port}`)));
(0, fs_1.writeFileSync)(filePath, content);
}
updatePortForFiles() {
/* istanbul ignore else */
if ((0, fs_1.existsSync)((0, path_1.join)(this.cwd, '.vscode', 'launch.json'))) {
this.updatePort((0, path_1.join)(this.cwd, '.vscode', 'launch.json'));
}
/* istanbul ignore else */
if ((0, fs_1.existsSync)((0, path_1.join)(this.cwd, '.idea', 'runConfigurations', 'Attach_FaaS_Debugger.xml'))) {
this.updatePort((0, path_1.join)(this.cwd, '.idea', 'runConfigurations', 'Attach_FaaS_Debugger.xml'));
}
}
}
exports.FaasDebugger = FaasDebugger;
/* istanbul ignore next */
if (process.argv.some((e) => e.includes('faas-debugger.js'))) {
/* istanbul ignore next */
new FaasDebugger().runDebugging();
}
//# sourceMappingURL=faas-debugger.js.map