lambda-live-debugger
Version:
Debug Lambda functions locally like it is running in the cloud
279 lines (278 loc) • 10.7 kB
JavaScript
import * as fs from 'fs/promises';
import * as path from 'path';
import { constants } from 'fs';
import { findPackageJson } from '../utils/findPackageJson.mjs';
import { Logger } from '../logger.mjs';
/**
* Support for Serverless Framework
*/
export class SlsFramework {
/**
* Framework name
*/
get name() {
return 'sls';
}
/**
* Can this class handle the current project
* @returns
*/
async canHandle() {
const serverlessFiles = [
path.resolve('serverless.yml'),
path.resolve('serverless.yaml'),
path.resolve('serverless.js'),
path.resolve('serverless.ts'),
path.resolve('serverless.json'),
];
for (const file of serverlessFiles) {
try {
await fs.access(file, constants.F_OK);
return true;
}
catch {
continue;
}
}
Logger.verbose(`[SLS] This is not a Serverless framework project. None of the files found: ${serverlessFiles.join(', ')}`);
return false;
}
/**
* Get Lambda functions
* @param config Configuration
* @returns Lambda functions
*/
async getLambdas(config) {
// LLD arguments might conflict with serverless arguments
process.argv = [];
let resolveConfigurationPath;
let readConfiguration;
let resolveVariables;
let resolveVariablesMeta;
let sources;
let Serverless;
let error1;
try {
try {
const frameworkFunctions = await loadFramework('serverless');
resolveConfigurationPath = frameworkFunctions.resolveConfigurationPath;
readConfiguration = frameworkFunctions.readConfiguration;
resolveVariables = frameworkFunctions.resolveVariables;
resolveVariablesMeta = frameworkFunctions.resolveVariablesMeta;
sources = frameworkFunctions.sources;
Serverless = frameworkFunctions.Serverless;
Logger.verbose(`[SLS] Npm module 'serverless' loaded`);
}
catch (error) {
Logger.verbose(`[SLS] Failed to load npm module 'serverless'`, error);
error1 = error;
const frameworkFunctions = await loadFramework('osls');
resolveConfigurationPath = frameworkFunctions.resolveConfigurationPath;
readConfiguration = frameworkFunctions.readConfiguration;
resolveVariables = frameworkFunctions.resolveVariables;
resolveVariablesMeta = frameworkFunctions.resolveVariablesMeta;
sources = frameworkFunctions.sources;
Serverless = frameworkFunctions.Serverless;
Logger.verbose(`[SLS] Npm module 'osls' loaded`);
}
}
catch (error2) {
const error = error1 ?? error2;
Logger.error('Error loading serverless (or osls) module', error);
Logger.log('If you are running Lambda Live Debugger from a global installation, install Serverless Framework globally as well. If you are using monorepo, install Serverless Framework also in the project root folder. The fork of Serverless Framework https://github.com/oss-serverless/serverless is also supported.');
throw new Error(`Error loading serverless modules. ${error.message}`, {
cause: error,
});
}
const configurationPath = await resolveConfigurationPath();
Logger.verbose(`[SLS] Configuration path: ${path.resolve(configurationPath)}`);
const configuration = await readConfiguration(configurationPath);
Logger.verbose(`[SLS] Configuration:`, JSON.stringify(configuration, null, 2));
const serviceDir = process.cwd();
const configurationFilename = configuration && configurationPath.slice(serviceDir.length + 1);
Logger.verbose(`[SLS] Configuration filename: ${path.resolve(configurationFilename)}`);
const commands = [];
const options = {};
if (config.stage) {
options.stage = config.stage;
}
if (config.region) {
options.region = config.region;
}
if (config.profile) {
options.profile = config.profile;
}
const variablesMeta = resolveVariablesMeta(configuration);
await resolveVariables({
serviceDir,
configuration,
variablesMeta,
sources,
options,
fulfilledSources: new Set(),
});
let serverless;
try {
serverless = new Serverless({
configuration,
serviceDir,
configurationFilename,
commands,
options,
variablesMeta,
});
}
catch (error) {
throw new Error(`Error creating Serverless instance. ${error.message}`, {
cause: error,
});
}
try {
await serverless.init();
}
catch (error) {
throw new Error(`Error initializing Serverless. ${error.message}`, {
cause: error,
});
}
try {
await serverless.run();
}
catch (error) {
throw new Error(`Error running Serverless. ${error.message}`, {
cause: error,
});
}
const lambdasDiscovered = [];
const esBuildOptions = this.getEsBuildOptions(serverless, config);
const lambdas = serverless.service.functions;
Logger.verbose(`[SLS] Found Lambdas:`, JSON.stringify(lambdas, null, 2));
for (const func in lambdas) {
const lambda = lambdas[func];
const handlerFull = lambda.handler;
const handlerParts = handlerFull.split('.');
const handler = handlerParts[1];
const possibleCodePaths = [
`${handlerParts[0]}.ts`,
`${handlerParts[0]}.js`,
`${handlerParts[0]}.cjs`,
`${handlerParts[0]}.mjs`,
];
let codePath;
for (const cp of possibleCodePaths) {
try {
await fs.access(cp, constants.F_OK);
codePath = cp;
break;
}
catch {
// ignore, file not found
}
}
if (!codePath) {
throw new Error(`Code path not found for handler: ${handlerFull}`);
}
const functionName = lambda.name;
if (!functionName) {
throw new Error(`Function name not found for handler: ${handlerFull}`);
}
const packageJsonPath = await findPackageJson(codePath);
Logger.verbose(`[SLS] package.json path: ${packageJsonPath}`);
const lambdaResource = {
functionName,
codePath,
handler,
packageJsonPath,
esBuildOptions,
metadata: {
framework: 'sls',
},
};
lambdasDiscovered.push(lambdaResource);
}
return lambdasDiscovered;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getEsBuildOptions(serverless, config) {
// 1) Get from from LLD specific options in custom.lldEsBuild
let esBuildOptions = serverless.service.custom?.lldEsBuild;
// 2) Get from serverless-esbuild plugin
const esBuildPlugin = serverless.service.plugins?.find((p) => p === 'serverless-esbuild');
if (esBuildPlugin) {
Logger.verbose('[SLS] serverless-esbuild plugin detected');
const settings = serverless.service.custom?.esbuild;
if (settings) {
esBuildOptions = {
minify: settings.minify,
target: settings.target,
external: settings.external,
};
}
}
else {
// 3) Get from serverless-plugin-typescript plugin
const typeScriptPlugin = serverless.service.plugins?.find((p) => p === 'serverless-plugin-typescript');
if (typeScriptPlugin) {
Logger.verbose('[SLS] serverless-plugin-typescript plugin detected');
const settings = serverless.service.custom?.serverlessPluginTypescript;
if (settings) {
esBuildOptions = {
tsconfig: path.resolve(settings.tsConfigFileLocation),
};
}
}
}
return esBuildOptions;
}
}
export const slsFramework = new SlsFramework();
async function loadFramework(npmName) {
// lazy load modules
const resolveConfigurationPath = (await import(
//@ts-ignore
`${npmName}/lib/cli/resolve-configuration-path.js`)).default;
const readConfiguration = (await import(
//@ts-ignore
`${npmName}/lib/configuration/read.js`)).default;
const resolveVariables = (await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/resolve.js`)).default;
const resolveVariablesMeta = (await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/resolve-meta.js`)).default;
const env = await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/sources/env.js`);
const file = await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/sources/file.js`);
const opt = await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/sources/opt.js`);
const self = await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/sources/self.js`);
const strToBool = await import(
//@ts-ignore
`${npmName}/lib/configuration/variables/sources/str-to-bool.js`);
const sls = await import(
//@ts-ignores
`${npmName}/lib/configuration/variables/sources/instance-dependent/get-sls.js`);
const sources = {
env: env.default,
file: file.default,
opt: opt.default,
self: self.default,
strToBool: strToBool.default,
sls: sls.default(),
};
const Serverless = (await import(npmName)).default;
return {
resolveConfigurationPath,
readConfiguration,
resolveVariablesMeta,
resolveVariables,
sources,
Serverless,
};
}