Skip to content

Enhance configuration management and add user prompts for missing values#1538

Open
KalinduGandara wants to merge 1 commit into
wso2:release-4.2.xfrom
KalinduGandara:config_improvement
Open

Enhance configuration management and add user prompts for missing values#1538
KalinduGandara wants to merge 1 commit into
wso2:release-4.2.xfrom
KalinduGandara:config_improvement

Conversation

@KalinduGandara

Copy link
Copy Markdown

This pull request introduces a pre-launch check for missing configurable values in .env files for each project before starting a debug session. If any required configurables are missing, the user is prompted to either proceed or add the missing values using a popup visual editor. The main changes add utility functions to parse and validate configurables, integrate the check into the debug configuration provider, and handle the popup UI flow.

Configurable value validation and UI integration:

  • Added logic in MiConfigurationProvider.resolveDebugConfiguration to check for missing configurable values in .env files for each project and prompt the user to add them before launching the debugger.
  • Implemented getConfigurablesWithValues to parse required configurables from config.properties and match them with values from .env.
  • Added confirmConfigurableValues to prompt the user if any configurables are missing, offering the option to open the Manage Configurables popup or proceed.
  • Added openManageConfigurables to open the appropriate visual editor for managing configurables, ensuring the webview is ready before opening the popup.

Imports and dependencies:

  • Updated imports to include new types and functions required for the configurables management and popup integration.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d911e9c7-436f-4111-8f38-f65aaab9f6c4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mi-extension/src/debugger/activate.ts`:
- Around line 435-467: Wrap the synchronous reads in the configurables-loading
function, including the optional env file read and required config file read, in
a try/catch so filesystem errors do not propagate or crash debug configuration
resolution. On failure, return the established graceful fallback (an empty
configurables list), while preserving the current parsing behavior for
successfully read files.
- Around line 500-513: Update the Promise waiting for viewReady in the debug
resolution flow to include a bounded timeout, ensuring it resolves when the
state machine never transitions or the overview closes. Clean up the transition
listener on both the viewReady path and timeout path, while preserving the
immediate resolution when currentState is already viewReady.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd7251b1-3837-4a9e-8f67-17e3b59b02d2

📥 Commits

Reviewing files that changed from the base of the PR and between 43f014a and d886384.

📒 Files selected for processing (1)
  • packages/mi-extension/src/debugger/activate.ts

Comment on lines +435 to +467
if (fs.existsSync(envPath)) {
for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf('=');
if (separatorIndex === -1) {
continue;
}
const key = trimmedLine.substring(0, separatorIndex).trim();
const value = trimmedLine.substring(separatorIndex + 1).trim();
if (key && value) {
envValues.set(key, value);
}
}
}

const configurables: PomNodeDetails[] = [];
for (const line of fs.readFileSync(configFilePath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf(':');
const key = (separatorIndex === -1 ? trimmedLine : trimmedLine.substring(0, separatorIndex)).trim();
const type = separatorIndex === -1 ? 'string' : (trimmedLine.substring(separatorIndex + 1).trim() || 'string');
if (key) {
configurables.push({ key, type, value: envValues.get(key) ?? '' });
}
}
return configurables;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle potential file read exceptions.

Synchronous file reads using fs.readFileSync can throw exceptions if the file is locked or if there are permission issues. An unhandled exception here will propagate and crash the debug configuration resolution. Wrap the file reading operations in a try/catch block to ensure a graceful fallback if the files are inaccessible. As per path instructions, ensure correctness and best practices.

🛡️ Proposed fix to handle file read exceptions
     const envValues: Map<string, string> = new Map();
     const envPath = path.join(projectUri, '.env');
     if (fs.existsSync(envPath)) {
+        try {
             for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
                 const trimmedLine = line.trim();
                 if (!trimmedLine || trimmedLine.startsWith('#')) {
                     continue;
                 }
                 const separatorIndex = trimmedLine.indexOf('=');
                 if (separatorIndex === -1) {
                     continue;
                 }
                 const key = trimmedLine.substring(0, separatorIndex).trim();
                 const value = trimmedLine.substring(separatorIndex + 1).trim();
                 if (key && value) {
                     envValues.set(key, value);
                 }
             }
+        } catch (error) {
+            console.error(`Failed to read .env file at ${envPath}:`, error);
+        }
     }
 
     const configurables: PomNodeDetails[] = [];
+    try {
         for (const line of fs.readFileSync(configFilePath, 'utf-8').split('\n')) {
             const trimmedLine = line.trim();
             if (!trimmedLine || trimmedLine.startsWith('#')) {
                 continue;
             }
             const separatorIndex = trimmedLine.indexOf(':');
             const key = (separatorIndex === -1 ? trimmedLine : trimmedLine.substring(0, separatorIndex)).trim();
             const type = separatorIndex === -1 ? 'string' : (trimmedLine.substring(separatorIndex + 1).trim() || 'string');
             if (key) {
                 configurables.push({ key, type, value: envValues.get(key) ?? '' });
             }
         }
+    } catch (error) {
+        console.error(`Failed to read config.properties at ${configFilePath}:`, error);
+    }
     return configurables;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (fs.existsSync(envPath)) {
for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf('=');
if (separatorIndex === -1) {
continue;
}
const key = trimmedLine.substring(0, separatorIndex).trim();
const value = trimmedLine.substring(separatorIndex + 1).trim();
if (key && value) {
envValues.set(key, value);
}
}
}
const configurables: PomNodeDetails[] = [];
for (const line of fs.readFileSync(configFilePath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf(':');
const key = (separatorIndex === -1 ? trimmedLine : trimmedLine.substring(0, separatorIndex)).trim();
const type = separatorIndex === -1 ? 'string' : (trimmedLine.substring(separatorIndex + 1).trim() || 'string');
if (key) {
configurables.push({ key, type, value: envValues.get(key) ?? '' });
}
}
return configurables;
}
if (fs.existsSync(envPath)) {
try {
for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf('=');
if (separatorIndex === -1) {
continue;
}
const key = trimmedLine.substring(0, separatorIndex).trim();
const value = trimmedLine.substring(separatorIndex + 1).trim();
if (key && value) {
envValues.set(key, value);
}
}
} catch (error) {
console.error(`Failed to read .env file at ${envPath}:`, error);
}
}
const configurables: PomNodeDetails[] = [];
try {
for (const line of fs.readFileSync(configFilePath, 'utf-8').split('\n')) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith('#')) {
continue;
}
const separatorIndex = trimmedLine.indexOf(':');
const key = (separatorIndex === -1 ? trimmedLine : trimmedLine.substring(0, separatorIndex)).trim();
const type = separatorIndex === -1 ? 'string' : (trimmedLine.substring(separatorIndex + 1).trim() || 'string');
if (key) {
configurables.push({ key, type, value: envValues.get(key) ?? '' });
}
}
} catch (error) {
console.error(`Failed to read config.properties at ${configFilePath}:`, error);
}
return configurables;
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 435-435: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(envPath, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)


[warning] 453-453: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(configFilePath, 'utf-8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mi-extension/src/debugger/activate.ts` around lines 435 - 467, Wrap
the synchronous reads in the configurables-loading function, including the
optional env file read and required config file read, in a try/catch so
filesystem errors do not propagate or crash debug configuration resolution. On
failure, return the established graceful fallback (an empty configurables list),
while preserving the current parsing behavior for successfully read files.

Source: Path instructions

Comment on lines +500 to +513
await new Promise<void>((resolve) => {
const stateMachine = getStateMachine(projectUri);
const currentState = stateMachine.state() as any;
if (typeof currentState === 'object' && currentState?.ready === 'viewReady') {
return resolve();
}
const listener = (state: { value: { ready?: string; }; }) => {
if (state?.value?.ready === 'viewReady') {
stateMachine.service().off(listener);
resolve();
}
};
stateMachine.service().onTransition(listener);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Prevent potential hang during debug resolution with a timeout.

The Promise waiting for the viewReady state lacks a timeout. If the state machine fails to transition (e.g., an error during view load) or if the user closes the overview before it finishes loading, the promise will hang indefinitely. Because this function is awaited in the debug configuration resolution path, an unresolved promise will block resolveDebugConfiguration and leave the debugging session in a locked, unresponsive state. As per path instructions, ensure correctness and best practices.

⏱️ Proposed fix to add a timeout
         await new Promise<void>((resolve) => {
             const stateMachine = getStateMachine(projectUri);
             const currentState = stateMachine.state() as any;
             if (typeof currentState === 'object' && currentState?.ready === 'viewReady') {
                 return resolve();
             }
+            let timeoutId: ReturnType<typeof setTimeout>;
             const listener = (state: { value: { ready?: string; }; }) => {
                 if (state?.value?.ready === 'viewReady') {
+                    clearTimeout(timeoutId);
                     stateMachine.service().off(listener);
                     resolve();
                 }
             };
+            timeoutId = setTimeout(() => {
+                stateMachine.service().off(listener);
+                console.warn('Timeout waiting for state machine to reach viewReady');
+                resolve();
+            }, 5000);
             stateMachine.service().onTransition(listener);
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await new Promise<void>((resolve) => {
const stateMachine = getStateMachine(projectUri);
const currentState = stateMachine.state() as any;
if (typeof currentState === 'object' && currentState?.ready === 'viewReady') {
return resolve();
}
const listener = (state: { value: { ready?: string; }; }) => {
if (state?.value?.ready === 'viewReady') {
stateMachine.service().off(listener);
resolve();
}
};
stateMachine.service().onTransition(listener);
});
await new Promise<void>((resolve) => {
const stateMachine = getStateMachine(projectUri);
const currentState = stateMachine.state() as any;
if (typeof currentState === 'object' && currentState?.ready === 'viewReady') {
return resolve();
}
let timeoutId: ReturnType<typeof setTimeout>;
const listener = (state: { value: { ready?: string; }; }) => {
if (state?.value?.ready === 'viewReady') {
clearTimeout(timeoutId);
stateMachine.service().off(listener);
resolve();
}
};
timeoutId = setTimeout(() => {
stateMachine.service().off(listener);
console.warn('Timeout waiting for state machine to reach viewReady');
resolve();
}, 5000);
stateMachine.service().onTransition(listener);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mi-extension/src/debugger/activate.ts` around lines 500 - 513,
Update the Promise waiting for viewReady in the debug resolution flow to include
a bounded timeout, ensuring it resolves when the state machine never transitions
or the overview closes. Clean up the transition listener on both the viewReady
path and timeout path, while preserving the immediate resolution when
currentState is already viewReady.

Source: Path instructions

context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('mi', factory));
}

function getConfigurablesWithValues(projectUri: string): PomNodeDetails[] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a LS method that we could use for this. Please check

return false;
}

async function openManageConfigurables(projectUri: string, configs: PomNodeDetails[]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without opening the same configuration popup page shall we open a dialog box to get the values only? Refer to the Remote Deployment feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants