Skip to content

Commit 1f9e589

Browse files
feat: JetBrains IDE tools support
1 parent aeb430d commit 1f9e589

File tree

8 files changed

+703
-96
lines changed

8 files changed

+703
-96
lines changed

package-lock.json

Lines changed: 446 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"chokidar": "^3.6.0",
116116
"commander": "^13.1.0",
117117
"esbuild": "^0.25.0",
118+
"fast-xml-parser": "^5.0.8",
118119
"inquirer": "^12.4.2",
119120
"jsonc-parser": "^3.3.1",
120121
"node-machine-id": "^1.1.12",
@@ -133,6 +134,7 @@
133134
"aws-iot-device-sdk",
134135
"chokidar",
135136
"commander",
137+
"fast-xml-parser",
136138
"node-machine-id",
137139
"toml",
138140
"yaml",

src/configuration/getConfigFromWizard.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { GitIgnore } from '../gitignore.js';
1717
import { VsCode } from '../vsCode.js';
1818
import { Logger } from '../logger.js';
1919
import { Configuration } from '../configuration.js';
20+
import { JetBrains } from '../jetBrains.js';
2021

2122
const configFileName = path.resolve(configFileDefaultName);
2223

@@ -335,6 +336,19 @@ export async function getConfigFromWizard({
335336
answers.vscode = answersVsCode.vscode;
336337
}
337338

339+
if (!(await JetBrains.isConfigured())) {
340+
const answersJetBrains = await inquirer.prompt([
341+
{
342+
type: 'confirm',
343+
name: 'jetbrains',
344+
message: `Would you like to add configuration for JetBrains IDE, like WebStorm?`,
345+
default: false,
346+
},
347+
]);
348+
349+
answers.jetbrains = answersJetBrains.jetbrains;
350+
}
351+
338352
const answersVerbose = await inquirer.prompt([
339353
{
340354
type: 'confirm',
@@ -464,6 +478,7 @@ function getConfigFromAnswers(answers: any): LldConfigCliArgs {
464478
interactive: answers.interactive,
465479
gitignore: answers.gitignore,
466480
vscode: answers.vscode,
481+
jetbrains: answers.jetbrains,
467482
};
468483

469484
//remove undefined and empty strings

src/jetBrains.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import fs from 'fs/promises';
2+
import path from 'path';
3+
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
4+
import { getProjectDirname } from './getDirname.js';
5+
import { Logger } from './logger.js';
6+
import { getRuntimeExecutableForIde } from './utils/getRuntimeExecutableForIde.js';
7+
8+
const workspaceXmlPath = path.join(
9+
getProjectDirname(),
10+
'.idea',
11+
'workspace.xml',
12+
);
13+
14+
async function getWebStormLaunchConfig() {
15+
let runtimeExecutable = await getRuntimeExecutableForIde();
16+
runtimeExecutable = runtimeExecutable.replace(
17+
'${workspaceFolder}',
18+
'$PROJECT_DIR$',
19+
);
20+
return {
21+
configuration: {
22+
'@_name': 'Lambda Live Debugger',
23+
'@_type': 'NodeJSConfigurationType',
24+
'@_path-to-js-file': runtimeExecutable,
25+
'@_working-dir': '$PROJECT_DIR$',
26+
method: { '@_v': '2' },
27+
},
28+
};
29+
}
30+
31+
async function readWorkspaceXml(
32+
filePath: string,
33+
): Promise<{ json: any; xmlString: string }> {
34+
try {
35+
const xmlString = await fs.readFile(filePath, 'utf-8');
36+
const parser = new XMLParser({
37+
ignoreAttributes: false,
38+
allowBooleanAttributes: true,
39+
});
40+
const json = parser.parse(xmlString);
41+
return { json, xmlString };
42+
} catch (err: any) {
43+
if (err.code === 'ENOENT') {
44+
return { json: null, xmlString: '' };
45+
}
46+
throw new Error(`Error reading ${filePath}`, { cause: err });
47+
}
48+
}
49+
50+
async function writeWorkspaceXml(filePath: string, json: any) {
51+
try {
52+
const builder = new XMLBuilder({
53+
ignoreAttributes: false,
54+
format: true,
55+
suppressEmptyNode: true,
56+
suppressBooleanAttributes: false,
57+
});
58+
const xmlString = builder.build(json);
59+
await fs.writeFile(filePath, xmlString, 'utf-8');
60+
Logger.verbose(`Updated WebStorm configuration at ${filePath}`);
61+
} catch (err) {
62+
throw new Error(`Error writing ${filePath}`, { cause: err });
63+
}
64+
}
65+
66+
async function isConfigured() {
67+
const { json } = await readWorkspaceXml(workspaceXmlPath);
68+
if (!json) return false;
69+
70+
const components = Array.isArray(json.project?.component)
71+
? json.project.component
72+
: [json.project?.component];
73+
const runManager = components.find((c: any) => c['@_name'] === 'RunManager');
74+
if (!runManager) return false;
75+
76+
const configurations = runManager.configuration || [];
77+
return configurations.some(
78+
(c: any) => c['@_name'] === 'Lambda Live Debugger',
79+
);
80+
}
81+
82+
async function addConfiguration() {
83+
Logger.verbose('Adding WebStorm run/debug configuration');
84+
const { json } = await readWorkspaceXml(workspaceXmlPath);
85+
const config = await getWebStormLaunchConfig();
86+
87+
if (!json) {
88+
// Create new workspace.xml if it does not exist
89+
const newJson = {
90+
'?xml': { '@_version': '1.0', '@_encoding': 'UTF-8' },
91+
project: {
92+
'@_version': '4',
93+
component: {
94+
'@_name': 'RunManager',
95+
configuration: [config.configuration],
96+
},
97+
},
98+
};
99+
await fs.mkdir(path.dirname(workspaceXmlPath), { recursive: true });
100+
await writeWorkspaceXml(workspaceXmlPath, newJson);
101+
return;
102+
}
103+
104+
let runManager = json.project.component.find(
105+
(c: any) => c['@_name'] === 'RunManager',
106+
);
107+
108+
if (!runManager) {
109+
Logger.verbose('RunManager not found, creating new RunManager component');
110+
runManager = { '@_name': 'RunManager', configuration: [] };
111+
json.project.component.push(runManager);
112+
}
113+
114+
let configurations;
115+
if (!runManager.configuration) {
116+
configurations = [];
117+
} else if (!Array.isArray(runManager.configuration)) {
118+
configurations = [runManager.configuration];
119+
} else {
120+
configurations = runManager.configuration;
121+
}
122+
123+
const exists = configurations.some(
124+
(c: any) => c['@_name'] === config.configuration['@_name'],
125+
);
126+
if (!exists) {
127+
Logger.verbose('Adding new configuration to workspace.xml');
128+
runManager.configuration = [...configurations, config.configuration];
129+
await writeWorkspaceXml(workspaceXmlPath, json);
130+
} else {
131+
Logger.verbose('Configuration already exists in workspace.xml');
132+
}
133+
}
134+
135+
export const JetBrains = {
136+
isConfigured,
137+
addConfiguration,
138+
};

src/lldebugger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Logger } from './logger.js';
1717
import { getModuleDirname, getProjectDirname } from './getDirname.js';
1818
import { LambdaConnection } from './lambdaConnection.js';
1919
import inquirer from 'inquirer';
20+
import { JetBrains } from './jetBrains.js';
2021

2122
/**
2223
* Start the Lambda Live Debugger
@@ -46,6 +47,10 @@ async function run() {
4647
await VsCode.addConfiguration();
4748
}
4849

50+
if (Configuration.config.jetbrains) {
51+
await JetBrains.addConfiguration();
52+
}
53+
4954
if (!Configuration.config.start && !Configuration.config.remove) {
5055
return;
5156
}

src/types/lldConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export type LldConfigBase = {
7878
export type LldConfigCliArgs = {
7979
remove?: 'keep-layer' | 'all';
8080
vscode?: boolean;
81+
jetbrains?: boolean;
8182
gitignore?: boolean;
8283
config?: string;
8384
wizard?: boolean;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fs from 'fs/promises';
2+
import path from 'path';
3+
import { getModuleDirname, getProjectDirname } from '../getDirname.js';
4+
import { Logger } from '../logger.js';
5+
6+
/**
7+
* Get the runtime executable for the IDE, like WebStorm or VSCode
8+
* @returns
9+
*/
10+
export async function getRuntimeExecutableForIde() {
11+
let runtimeExecutable: string | undefined;
12+
const localRuntimeExecutable = '${workspaceFolder}/node_modules/.bin/lld';
13+
14+
const moduleDirname = getModuleDirname();
15+
const projectDirname = getProjectDirname();
16+
17+
const localFolder = path.resolve(
18+
path.join(projectDirname, 'node_modules/.bin/lld'),
19+
);
20+
21+
//if installed locally
22+
if (moduleDirname.startsWith('/home/')) {
23+
Logger.verbose('Lambda Live Debugger is installed locally');
24+
// check if file exists
25+
try {
26+
Logger.verbose(
27+
'Checking local folder for runtimeExecutable setting for VsCode configuration',
28+
localFolder,
29+
);
30+
await fs.access(localFolder, fs.constants.F_OK);
31+
runtimeExecutable = localRuntimeExecutable;
32+
} catch {
33+
// Not found
34+
}
35+
} else {
36+
Logger.verbose('Lambda Live Debugger is installed globally');
37+
}
38+
39+
if (!runtimeExecutable) {
40+
Logger.verbose(
41+
`Setting absolute path for runtimeExecutable setting for VsCode configuration`,
42+
);
43+
const localFolderSubfolder = path.resolve('node_modules/.bin/lld');
44+
const globalModule1 = path.join(moduleDirname, '..', '..', '.bin/lld');
45+
const globalModule2 = path.join(moduleDirname, '..', '..', 'bin/lld');
46+
const globalModule3 = path.join(
47+
moduleDirname,
48+
'..',
49+
'..',
50+
'..',
51+
'..',
52+
'bin/lld',
53+
);
54+
const possibleFolders = {
55+
[localFolder]: '${workspaceFolder}/node_modules/.bin/lld',
56+
[localFolderSubfolder]: localFolderSubfolder,
57+
[globalModule1]: globalModule1,
58+
[globalModule2]: globalModule2,
59+
[globalModule3]: globalModule3,
60+
};
61+
62+
Logger.verbose(
63+
`Checking the following possible folders for lld executable:`,
64+
JSON.stringify(possibleFolders, null, 2),
65+
);
66+
67+
// check each possible folder and set the runtimeExecutable
68+
for (const folder in possibleFolders) {
69+
try {
70+
//Logger.log("Checking folder", folder);
71+
await fs.access(folder, fs.constants.F_OK);
72+
runtimeExecutable = possibleFolders[folder];
73+
Logger.verbose(`Found folder with lld executable: ${folder}`);
74+
break;
75+
} catch {
76+
// Not found
77+
}
78+
}
79+
80+
if (!runtimeExecutable) {
81+
Logger.error(
82+
`Could not find lld executable. Please check your IDE debugger settings.`,
83+
);
84+
}
85+
}
86+
87+
if (!runtimeExecutable) {
88+
return localRuntimeExecutable;
89+
}
90+
91+
return runtimeExecutable;
92+
}

0 commit comments

Comments
 (0)