Files
bruno/packages/bruno-electron/src/ipc/global-environments.js
Chirag Chandrashekhar 13e97f0367 fix: preserve global environment color during script execution (#7427)
When executing requests with pre-request or post-response scripts, the
global environment color property was being stripped from YAML files.
This happened because the save operation only passed `variables` through
the IPC chain, and the workspace-environments store created a new
environment object without preserving the existing `color` property.

The fix passes the `color` property through the entire IPC chain:
- Redux actions now include `color` in the save-global-environment IPC call
- IPC handler accepts and forwards `color` to both stores
- workspace-environments store includes `color` when creating the environment object
- global-environments store preserves `color` when updating

Fixes #7348

Co-authored-by: Chirag Chandrashekhar <cchirag85@gmail.com>
2026-03-27 19:56:19 +05:30

273 lines
10 KiB
JavaScript

require('dotenv').config();
const fs = require('fs');
const path = require('path');
const { ipcMain } = require('electron');
const { utils: { jsonToDotenv } } = require('@usebruno/common');
const { globalEnvironmentsStore } = require('../store/global-environments');
const { generateUniqueName, sanitizeName, writeFile, isValidDotEnvFilename } = require('../utils/filesystem');
const { readWorkspaceConfig, writeWorkspaceConfig } = require('../utils/workspace-config');
/**
* Migrates activeEnvironmentUid from workspace.yml to the electron store (per-workspace).
* This handles users upgrading from versions that stored the active global env in workspace.yml.
*
* Fallback chain:
* 1. Per-workspace electron store (already migrated) - use it
* 2. workspace.yml activeEnvironmentUid - migrate to electron store, remove from file
* 3. Legacy electron store activeGlobalEnvironmentUid - migrate to per-workspace store
* 4. null (new workspace)
*/
const migrateActiveGlobalEnvironmentUid = async (workspacePath) => {
// Already in per-workspace store (null means explicitly "No Environment", undefined means not set)
const perWorkspaceUid = globalEnvironmentsStore.getActiveGlobalEnvironmentUidForWorkspace(workspacePath);
if (perWorkspaceUid !== undefined) {
return perWorkspaceUid;
}
// Try workspace.yml
try {
const config = readWorkspaceConfig(workspacePath);
if (config.activeEnvironmentUid) {
const uid = config.activeEnvironmentUid;
// Migrate to electron store
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(workspacePath, uid);
// Rewrite workspace.yml without activeEnvironmentUid (generateYamlContent drops unknown fields)
await writeWorkspaceConfig(workspacePath, config);
return uid;
}
} catch (error) {
// workspace.yml may not exist or be unreadable, continue to next fallback
}
// Fallback to legacy single active uid
const legacyUid = globalEnvironmentsStore.getActiveGlobalEnvironmentUid();
if (legacyUid) {
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(workspacePath, legacyUid);
return legacyUid;
}
return null;
};
const registerGlobalEnvironmentsIpc = (mainWindow, workspaceEnvironmentsManager) => {
ipcMain.handle('renderer:create-global-environment', async (event, { uid, name, variables, color, workspaceUid, workspacePath }) => {
try {
// If workspace path provided, use workspace environments manager
if (workspacePath && workspaceEnvironmentsManager) {
const { globalEnvironments } = await workspaceEnvironmentsManager.getGlobalEnvironmentsByPath(workspacePath);
const existingNames = globalEnvironments?.map((env) => env.name) || [];
const sanitizedName = sanitizeName(name);
const uniqueName = generateUniqueName(sanitizedName, (name) => existingNames.includes(name));
return await workspaceEnvironmentsManager.addGlobalEnvironmentByPath(workspacePath, { uid, name: uniqueName, variables, color });
}
const existingGlobalEnvironments = globalEnvironmentsStore.getGlobalEnvironments();
const existingNames = existingGlobalEnvironments?.map((env) => env.name) || [];
const sanitizedName = sanitizeName(name);
const uniqueName = generateUniqueName(sanitizedName, (name) => existingNames.includes(name));
globalEnvironmentsStore.addGlobalEnvironment({ uid, name: uniqueName, variables, color });
return { name: uniqueName, color };
} catch (error) {
console.error('Error in renderer:create-global-environment:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:save-global-environment', async (event, { environmentUid, variables, color, workspaceUid, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.saveGlobalEnvironmentByPath(workspacePath, { environmentUid, variables, color });
}
globalEnvironmentsStore.saveGlobalEnvironment({ environmentUid, variables, color });
} catch (error) {
console.error('Error in renderer:save-global-environment:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:rename-global-environment', async (event, { environmentUid, name, workspaceUid, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.renameGlobalEnvironmentByPath(workspacePath, { environmentUid, name });
}
globalEnvironmentsStore.renameGlobalEnvironment({ environmentUid, name });
} catch (error) {
console.error('Error in renderer:rename-global-environment:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:delete-global-environment', async (event, { environmentUid, workspaceUid, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
await workspaceEnvironmentsManager.deleteGlobalEnvironmentByPath(workspacePath, { environmentUid });
// Clear active environment for this workspace if the deleted one was active
const activeUid = globalEnvironmentsStore.getActiveGlobalEnvironmentUidForWorkspace(workspacePath);
if (activeUid === environmentUid) {
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(workspacePath, null);
}
} else {
globalEnvironmentsStore.deleteGlobalEnvironment({ environmentUid });
}
} catch (error) {
console.error('Error in renderer:delete-global-environment:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:select-global-environment', async (event, { environmentUid, workspacePath }) => {
try {
if (workspacePath) {
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(workspacePath, environmentUid || null);
} else {
globalEnvironmentsStore.setActiveGlobalEnvironmentUid(environmentUid || null);
}
} catch (error) {
console.error('Error in renderer:select-global-environment:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:get-global-environments', async (event, { workspaceUid, workspacePath }) => {
try {
let globalEnvironments = [];
if (workspacePath && workspaceEnvironmentsManager) {
const result = await workspaceEnvironmentsManager.getGlobalEnvironmentsByPath(workspacePath);
globalEnvironments = result?.globalEnvironments || [];
} else {
globalEnvironments = globalEnvironmentsStore.getGlobalEnvironments() || [];
}
const activeGlobalEnvironmentUid = workspacePath
? await migrateActiveGlobalEnvironmentUid(workspacePath)
: globalEnvironmentsStore.getActiveGlobalEnvironmentUid();
return {
globalEnvironments,
activeGlobalEnvironmentUid
};
} catch (error) {
console.error('Error in renderer:get-global-environments:', error);
return Promise.reject(error);
}
});
// Save workspace .env file variables
ipcMain.handle('renderer:save-workspace-dotenv-variables', async (event, { workspacePath, variables, filename = '.env' }) => {
try {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
if (!isValidDotEnvFilename(filename)) {
throw new Error('Invalid .env filename');
}
const dotEnvPath = path.join(workspacePath, filename);
const content = jsonToDotenv(variables);
await writeFile(dotEnvPath, content);
return { success: true };
} catch (error) {
console.error('Error saving workspace .env file:', error);
return Promise.reject(error);
}
});
// Save workspace .env file raw content
ipcMain.handle('renderer:save-workspace-dotenv-raw', async (event, { workspacePath, content, filename = '.env' }) => {
try {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
if (!isValidDotEnvFilename(filename)) {
throw new Error('Invalid .env filename');
}
const dotEnvPath = path.join(workspacePath, filename);
await writeFile(dotEnvPath, content);
return { success: true };
} catch (error) {
console.error('Error saving workspace .env file:', error);
return Promise.reject(error);
}
});
// Create workspace .env file
ipcMain.handle('renderer:create-workspace-dotenv-file', async (event, { workspacePath, filename = '.env' }) => {
try {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
if (!isValidDotEnvFilename(filename)) {
throw new Error('Invalid .env filename');
}
const dotEnvPath = path.join(workspacePath, filename);
if (fs.existsSync(dotEnvPath)) {
throw new Error(`${filename} file already exists`);
}
await writeFile(dotEnvPath, '');
return { success: true };
} catch (error) {
console.error('Error creating workspace .env file:', error);
return Promise.reject(error);
}
});
// Delete workspace .env file
ipcMain.handle('renderer:delete-workspace-dotenv-file', async (event, { workspacePath, filename = '.env' }) => {
try {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
if (!isValidDotEnvFilename(filename)) {
throw new Error('Invalid .env filename');
}
const dotEnvPath = path.join(workspacePath, filename);
if (!fs.existsSync(dotEnvPath)) {
throw new Error(`${filename} file does not exist`);
}
fs.unlinkSync(dotEnvPath);
return { success: true };
} catch (error) {
console.error('Error deleting workspace .env file:', error);
return Promise.reject(error);
}
});
ipcMain.handle('renderer:update-global-environment-color', async (event, { environmentUid, color, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color });
}
globalEnvironmentsStore.updateGlobalEnvironmentColor({ environmentUid, color });
} catch (error) {
console.error('Error in renderer:update-global-environment-color:', error);
return Promise.reject(error);
}
});
};
module.exports = registerGlobalEnvironmentsIpc;