lambda-live-debugger
Version:
Debug Lambda functions locally like it is running in the cloud
144 lines (143 loc) • 5.19 kB
JavaScript
import fs from 'fs/promises';
import path from 'path';
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
import { getProjectDirname } from './getDirname.mjs';
import { Logger } from './logger.mjs';
import { getRuntimeExecutableForIde } from './utils/getRuntimeExecutableForIde.mjs';
const configName = 'Lambda Live Debugger';
const workspaceXmlPath = path.join(getProjectDirname(), '.idea', 'workspace.xml');
async function getJetBrainsLaunchConfig() {
let runtimeExecutable = await getRuntimeExecutableForIde(false);
if (!runtimeExecutable) {
return undefined;
}
runtimeExecutable = runtimeExecutable.replace('${workspaceFolder}', '$PROJECT_DIR$');
return {
configuration: {
'@_name': 'Lambda Live Debugger',
'@_type': 'NodeJSConfigurationType',
'@_path-to-js-file': runtimeExecutable,
'@_working-dir': '$PROJECT_DIR$',
method: { '@_v': '2' },
},
};
}
async function readWorkspaceXml(filePath) {
try {
const xmlString = await fs.readFile(filePath, 'utf-8');
const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
});
const json = parser.parse(xmlString);
return { json, xmlString };
}
catch (err) {
if (err.code === 'ENOENT') {
return { json: null, xmlString: '' };
}
throw new Error(`Error reading ${filePath}`, { cause: err });
}
}
async function writeWorkspaceXml(filePath, json) {
try {
const builder = new XMLBuilder({
ignoreAttributes: false,
format: true,
suppressEmptyNode: true,
suppressBooleanAttributes: false,
});
const xmlString = builder.build(json);
await fs.writeFile(filePath, xmlString, 'utf-8');
Logger.verbose(`Updated JetBrains IDE configuration at ${filePath}`);
}
catch (err) {
throw new Error(`Error writing ${filePath}`, { cause: err });
}
}
async function isConfigured() {
try {
const { json } = await readWorkspaceXml(workspaceXmlPath);
if (!json)
return false;
const { lldConfigExists } = extractWorkspaceData(json);
return lldConfigExists;
}
catch (err) {
Logger.error('Error checking JetBrains IDE configuration', err);
return true;
}
}
async function addConfiguration() {
try {
Logger.verbose('Adding JetBrains IDE run/debug configuration');
const { json } = await readWorkspaceXml(workspaceXmlPath);
const config = await getJetBrainsLaunchConfig();
if (!config) {
Logger.error('Failed to configure JetBrains IDE. Cannot find a locally installed Lambda Live Debugger. The JetBrains IDE debugger cannot use a globally installed version.');
return;
}
if (!json) {
// Create new workspace.xml if it does not exist
const newJson = {
'?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' },
project: {
'@_version': '4',
component: {
'@_name': 'RunManager',
configuration: [config.configuration],
},
},
};
await fs.mkdir(path.dirname(workspaceXmlPath), { recursive: true });
await writeWorkspaceXml(workspaceXmlPath, newJson);
return;
}
const { lldConfigExists, components, runManager, configurations } = extractWorkspaceData(json);
json.project.component = components;
if (!lldConfigExists) {
Logger.verbose('Adding new configuration to workspace.xml');
runManager.configuration = [...configurations, config.configuration];
await writeWorkspaceXml(workspaceXmlPath, json);
}
else {
Logger.verbose('Configuration already exists in workspace.xml');
}
}
catch (err) {
Logger.error('Error adding JetBrains IDE configuration', err);
}
}
/**
* Extract workspace data from JetBrains IDE configuration
* @param json
* @returns
*/
function extractWorkspaceData(json) {
let components = json.project.component;
if (!Array.isArray(components)) {
components = [components];
}
let runManager = components.find((c) => c['@_name'] === 'RunManager');
if (!runManager) {
Logger.verbose('RunManager not found, creating new RunManager component');
runManager = { '@_name': 'RunManager', configuration: [] };
components.push(runManager);
}
let configurations;
if (!runManager.configuration) {
configurations = [];
}
else if (!Array.isArray(runManager.configuration)) {
configurations = [runManager.configuration];
}
else {
configurations = runManager.configuration;
}
const lldConfigExists = configurations.some((c) => c['@_name'] === configName);
return { lldConfigExists, runManager, configurations, components };
}
export const JetBrains = {
isConfigured,
addConfiguration,
};