UNPKG

lambda-live-debugger

Version:

Debug Lambda functions locally like it is running in the cloud

410 lines (408 loc) 16.5 kB
import inquirer from 'inquirer'; import { defaultObservableInterval, configFileDefaultName, } from '../constants.mjs'; import path from 'path'; import fs from 'fs/promises'; import { ResourceDiscovery } from '../resourceDiscovery.mjs'; import { GitIgnore } from '../gitignore.mjs'; import { VsCode } from '../vsCode.mjs'; import { Logger } from '../logger.mjs'; import { Configuration } from '../configuration.mjs'; import { JetBrains } from '../jetBrains.mjs'; const configFileName = path.resolve(configFileDefaultName); /** * Get configuration from wizard * @param parameters * @returns */ export async function getConfigFromWizard({ configFromCliArgs, supportedFrameworks, currentFramework, currentConfig, }) { let lambdasList; try { let answers = await inquirer.prompt([ { type: 'list', name: 'framework', message: `Which framework are you using (detected: ${currentFramework ?? '?'})?`, choices: [...supportedFrameworks, 'other'], default: configFromCliArgs.framework ?? currentConfig?.framework ?? currentFramework, }, ]); if (answers.framework === 'other' || answers.framework === 'none') { answers.framework = undefined; } const oldContext = currentConfig?.context ?? []; if (configFromCliArgs.context?.length) { oldContext.push(...configFromCliArgs.context); } if (answers.framework === 'cdk') { const cdkAnswers = await inquirer.prompt([ { type: 'input', name: 'context', message: 'Would you like to enter CDK context (example: environment=development)?', default: oldContext.length > 0 ? oldContext.shift() : undefined, }, ]); if (cdkAnswers.context && cdkAnswers.context.trim() !== '') { answers.context = [cdkAnswers.context.trim()]; // more context while (true) { const moreContextAnswers = await inquirer.prompt([ { type: 'input', name: 'context', message: 'Would you like to enter more CDK context?', default: oldContext.length > 0 ? oldContext.shift() : undefined, }, ]); if (moreContextAnswers.context && moreContextAnswers.context.trim() !== '') { answers.context = [ ...(answers.context ?? []), moreContextAnswers.context.trim(), ]; } else { break; } } } } if (answers.framework === 'sls') { const slsAnswers = await inquirer.prompt([ { type: 'input', name: 'stage', message: 'Would you like to enter Serverless Framework stage?', default: configFromCliArgs.stage ?? currentConfig?.stage, }, ]); answers = { ...answers, ...slsAnswers }; } if (answers.framework === 'sam') { const samAnswers = await inquirer.prompt([ { type: 'input', name: 'configEnv', message: 'Would you like to enter SAM environment?', default: configFromCliArgs.configEnv ?? currentConfig?.configEnv, }, { type: 'input', name: 'samConfigFile', message: 'Would you like to enter SAM configuration file (default = samconfig.toml)?', default: configFromCliArgs.samConfigFile ?? currentConfig?.samConfigFile, }, { type: 'input', name: 'samTemplateFile', message: 'Would you like to enter SAM template file (default = template.yaml)?', default: configFromCliArgs.samTemplateFile ?? currentConfig?.samTemplateFile, }, { type: 'input', name: 'samStackName', message: 'Would you like to enter SAM stack name and not use the one from config?', default: configFromCliArgs.samStackName ?? currentConfig?.samStackName, }, ]); answers = { ...answers, ...samAnswers }; } // monorepo subfolder const answersSubfolder = await inquirer.prompt([ { type: 'input', name: 'subfolder', message: 'If you are using monorepo, enter the subfolder where the framework is installed.', default: configFromCliArgs.subfolder ?? currentConfig?.subfolder, }, ]); if (answersSubfolder.subfolder) { answers.subfolder = answersSubfolder.subfolder; process.chdir(answers.subfolder); } // do you want to use Observability mode? const answersObservable = await inquirer.prompt([ { type: 'confirm', name: 'observable', message: 'Do you want to use Observability mode, which just sends events to the debugger and does not use the response?', default: !!(configFromCliArgs.observable !== undefined ? configFromCliArgs.observable : currentConfig?.observable), }, ]); answers = { ...answers, ...answersObservable }; if (answers.observable) { const defaultInt = configFromCliArgs.interval !== undefined ? configFromCliArgs.interval : currentConfig?.interval !== undefined ? currentConfig?.interval : defaultObservableInterval; const observableAnswers = await inquirer.prompt([ { type: 'number', name: 'interval', message: `Would you like to enter Observability mode interval at which events are sent to the debugger? Default is ${defaultObservableInterval}`, default: defaultInt, }, ]); answers = { ...answers, interval: observableAnswers.interval === defaultObservableInterval ? undefined : observableAnswers.interval, }; } // do you want to manually approve AWS infrastructure changes? const answersApproval = await inquirer.prompt([ { type: 'confirm', name: 'approval', message: 'Before debugging, do you want to review and manually approve AWS infrastructure changes, like adding a Lambda layer?', default: currentConfig?.approval === true, }, ]); answers = { ...answers, ...answersApproval }; const answersAws = await inquirer.prompt([ { type: 'input', name: 'profile', message: 'Would you like to use named AWS profile?', default: configFromCliArgs.profile ?? currentConfig?.profile, }, { type: 'input', name: 'region', message: 'Would you like to specify AWS region?', default: configFromCliArgs.region ?? currentConfig?.region, }, { type: 'input', name: 'role', message: 'Would you like to specify AWS role?', default: configFromCliArgs.role ?? currentConfig?.role, }, ]); answers = { ...answers, ...answersAws }; // do you want to filter which Lambdas to debug? const answersFilter = await inquirer.prompt([ { type: 'list', name: 'function', message: 'Would you like to filter which Lambdas to debug?', choices: ['All', 'Pick one', 'Filter by name'], default: currentConfig?.function === undefined ? 'All' : (currentConfig?.function).includes('*') ? 'Filter by name' : 'Pick one', }, ]); if (answersFilter.function === 'Pick one') { // I need to use congiration settings I accquired so far to get the list of lambdas const configTemp = getConfigFromAnswers(answers); Configuration.setConfig(configTemp); // not complete config lambdasList = await ResourceDiscovery.getLambdas(getConfigFromAnswers(answers)); if (!lambdasList) { throw new Error('No Lambdas found'); } // get list of lambdas const lambdas = await inquirer.prompt([ { type: 'list', name: 'function', message: 'Pick Lambda to debug', choices: lambdasList.map((l) => l.functionName), default: currentConfig?.function ?? lambdasList[0].functionName, }, ]); answers.function = lambdas.function; lambdasList = [ lambdasList.find((l) => l.functionName === lambdas.function), ]; } else if (answersFilter.function === 'Filter by name') { const filter = await inquirer.prompt([ { type: 'input', name: 'name', message: 'Enter Lambda name to filter. Use * as wildcard', default: configFromCliArgs.function ?? currentConfig?.function, }, ]); answers.function = filter.name; lambdasList = undefined; // will be rediscovered later } let save = false; if (!answers.remove) { const answersSave = await inquirer.prompt([ { type: 'confirm', name: 'save', message: `Would you like to save these settings to ${configFileDefaultName}?`, }, ]); if (answersSave.save) { save = true; } if (!(await GitIgnore.doesExistInGitIgnore())) { const answersGitIgnore = await inquirer.prompt([ { type: 'confirm', name: 'gitignore', message: `Would you like to add ${configFileDefaultName} to .gitignore?`, }, ]); answers.gitignore = answersGitIgnore.gitignore; } if (!(await VsCode.isConfigured())) { const answersVsCode = await inquirer.prompt([ { type: 'confirm', name: 'vscode', message: `Would you like to add configuration to VsCode?`, }, ]); answers.vscode = answersVsCode.vscode; } if (!(await JetBrains.isConfigured())) { const answersJetBrains = await inquirer.prompt([ { type: 'confirm', name: 'jetbrains', message: `Would you like to add configuration for JetBrains IDE, like WebStorm?`, default: false, }, ]); answers.jetbrains = answersJetBrains.jetbrains; } const answersVerbose = await inquirer.prompt([ { type: 'confirm', name: 'verbose', message: 'Do you want to use verbose logging? This will log all events to the console.', default: currentConfig?.verbose === true, }, ]); answers.verbose = configFromCliArgs.verbose !== undefined ? configFromCliArgs.verbose : answersVerbose.verbose; } /* { type: "confirm", name: "observable", message: "Do you want to use observable mode, which just sends events to the debugger and do not use the respose?", default: false, }, */ const config = getConfigFromAnswers(answers); if (save) { await saveConfiguration(config); } return config; } catch (error) { if (error.name === 'ExitPromptError') { // user canceled the prompt process.exit(0); } else { throw error; } } } async function saveConfiguration(config) { Logger.log(`Saving to config file ${configFileName}`); // save to file that looks like this: const configContent = ` import { type LldConfigTs } from "lambda-live-debugger"; export default { // Framework to use framework: "${config.framework}", // AWS CDK framework context context: ${config.context ? JSON.stringify(config.context) : undefined}, // Serverless Framework stage stage: "${config.stage}", // Monorepo subfolder subfolder: "${config.subfolder}", // Filter by function name. You can use * as a wildcard function: "${config.function}", // AWS profile profile: "${config.profile}", // AWS region region: "${config.region}", // AWS role role: "${config.role}", // SAM framework environment configEnv: "${config.configEnv}", // SAM framework configuration file samConfigFile: "${config.samConfigFile}", // SAM framework template file samTemplateFile: "${config.samTemplateFile}", // SAM framework stack name samStackName: "${config.samStackName}", // Observable mode observable: ${config.observable}, // Observable mode interval interval: ${config.interval === defaultObservableInterval ? undefined : config.interval}, // Approval required for AWS infrastructure changes approval: ${config.approval}, // Verbose logging verbose: ${config.verbose}, // Modify Lambda function list or support custom framework // getLambdas: async (foundLambdas) => { // you can customize the list of Lambdas here or create your own // return foundLambdas; // }, } satisfies LldConfigTs; `; // comment lines that contains undefined or "" const configContentCleaned = configContent .trim() .split('\n') .map((l) => l.includes('undefined') ? ` // ${l .replace('"undefined",', '') .replace('undefined,', '') .trim()}` : l) .join('\n'); await fs.writeFile(configFileName, configContentCleaned); } function getConfigFromAnswers(answers) { const config = { remove: answers.remove, framework: answers.framework, context: answers.context, stage: answers.stage, subfolder: answers.subfolder, function: answers.function, profile: answers.profile, region: answers.region, role: answers.role, configEnv: answers.configEnv, samConfigFile: answers.samConfigFile, samTemplateFile: answers.samTemplateFile, samStackName: answers.samStackName, observable: answers.observable, interval: answers.interval !== undefined ? answers.interval : defaultObservableInterval, approval: answers.approval, verbose: answers.verbose, interactive: answers.interactive, gitignore: answers.gitignore, vscode: answers.vscode, jetbrains: answers.jetbrains, }; //remove undefined and empty strings Object.keys(config).forEach((key) => config[key] === undefined || config[key] === '' ? delete config[key] : {}); return config; }