UNPKG

lambda-live-debugger

Version:

Debug Lambda functions locally like it is running in the cloud

279 lines (278 loc) 10.7 kB
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, }; }