remove activeEnvironmentUid and migration (#7545)

* remove activeEnvironmentUid and migration

* fix: no environment handling

* fix: standardize workspace path handling
This commit is contained in:
naman-bruno
2026-03-23 21:02:53 +05:30
committed by GitHub
parent 32b9f527ea
commit f1d7f007fe
16 changed files with 401 additions and 136 deletions

View File

@@ -737,28 +737,6 @@ export const deleteWorkspaceEnvironment = (workspaceUid, environmentUid) => {
};
};
export const selectWorkspaceEnvironment = (workspaceUid, environmentUid) => {
return async (dispatch, getState) => {
try {
const workspace = getState().workspaces.workspaces.find((w) => w.uid === workspaceUid);
if (!workspace) {
throw new Error('Workspace not found');
}
await ipcRenderer.invoke('renderer:select-workspace-environment', workspace.pathname, environmentUid);
dispatch(updateWorkspace({
uid: workspaceUid,
activeEnvironmentUid: environmentUid
}));
return true;
} catch (error) {
throw error;
}
};
};
export const importWorkspaceEnvironment = (workspaceUid, environmentData) => {
return async (dispatch, getState) => {
try {

View File

@@ -5,6 +5,49 @@ 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 }) => {
@@ -64,23 +107,28 @@ const registerGlobalEnvironmentsIpc = (mainWindow, workspaceEnvironmentsManager)
ipcMain.handle('renderer:delete-global-environment', async (event, { environmentUid, workspaceUid, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.deleteGlobalEnvironmentByPath(workspacePath, { environmentUid });
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 });
}
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, workspaceUid, workspacePath }) => {
ipcMain.handle('renderer:select-global-environment', async (event, { environmentUid, workspacePath }) => {
try {
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.selectGlobalEnvironmentByPath(workspacePath, { environmentUid });
if (workspacePath) {
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(workspacePath, environmentUid || null);
} else {
globalEnvironmentsStore.setActiveGlobalEnvironmentUid(environmentUid || null);
}
globalEnvironmentsStore.selectGlobalEnvironment({ environmentUid });
} catch (error) {
console.error('Error in renderer:select-global-environment:', error);
return Promise.reject(error);
@@ -89,13 +137,22 @@ const registerGlobalEnvironmentsIpc = (mainWindow, workspaceEnvironmentsManager)
ipcMain.handle('renderer:get-global-environments', async (event, { workspaceUid, workspacePath }) => {
try {
let globalEnvironments = [];
if (workspacePath && workspaceEnvironmentsManager) {
return await workspaceEnvironmentsManager.getGlobalEnvironmentsByPath(workspacePath);
const result = await workspaceEnvironmentsManager.getGlobalEnvironmentsByPath(workspacePath);
globalEnvironments = result?.globalEnvironments || [];
} else {
globalEnvironments = globalEnvironmentsStore.getGlobalEnvironments() || [];
}
const activeGlobalEnvironmentUid = workspacePath
? await migrateActiveGlobalEnvironmentUid(workspacePath)
: globalEnvironmentsStore.getActiveGlobalEnvironmentUid();
return {
globalEnvironments: globalEnvironmentsStore.getGlobalEnvironments() || [],
activeGlobalEnvironmentUid: globalEnvironmentsStore.getActiveGlobalEnvironmentUid()
globalEnvironments,
activeGlobalEnvironmentUid
};
} catch (error) {
console.error('Error in renderer:get-global-environments:', error);

View File

@@ -10,6 +10,7 @@ const yaml = require('js-yaml');
const LastOpenedWorkspaces = require('../store/last-opened-workspaces');
const { defaultWorkspaceManager } = require('../store/default-workspace');
const { globalEnvironmentsManager } = require('../store/workspace-environments');
const { globalEnvironmentsStore } = require('../store/global-environments');
const {
createWorkspaceConfig,
@@ -280,6 +281,7 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
ipcMain.handle('renderer:close-workspace', async (event, workspacePath) => {
try {
lastOpenedWorkspaces.remove(workspacePath);
globalEnvironmentsStore.removeActiveGlobalEnvironmentUidForWorkspace(workspacePath);
if (workspaceWatcher) {
workspaceWatcher.removeWatcher(workspacePath);
@@ -468,14 +470,6 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => {
}
});
ipcMain.handle('renderer:select-workspace-environment', async (event, workspacePath, environmentUid) => {
try {
return await globalEnvironmentsManager.selectGlobalEnvironment(workspacePath, { environmentUid });
} catch (error) {
throw error;
}
});
ipcMain.handle('renderer:import-workspace-environment', async (event, workspacePath, environmentData) => {
try {
return await globalEnvironmentsManager.createGlobalEnvironment(workspacePath, {

View File

@@ -66,7 +66,7 @@ class DefaultWorkspaceManager {
* Recovers collections and environments from an existing workspace directory
*/
recoverDataFromWorkspace(workspacePath) {
const recovered = { collections: [], environments: [], activeEnvironmentUid: null };
const recovered = { collections: [], environments: [] };
try {
// Try to read workspace config for collections
@@ -78,9 +78,6 @@ class DefaultWorkspaceManager {
return isValidCollectionDirectory(collectionPath);
});
}
if (config.activeEnvironmentUid) {
recovered.activeEnvironmentUid = config.activeEnvironmentUid;
}
} catch (error) {
console.error('Failed to read workspace config during recovery:', error);
}
@@ -273,9 +270,6 @@ class DefaultWorkspaceManager {
console.error('Failed to copy environment:', env.name, error);
}
}
if (recoveredData.activeEnvironmentUid) {
workspaceConfig.activeEnvironmentUid = recoveredData.activeEnvironmentUid;
}
}
// Apply recovered collections first (lower priority)
@@ -374,8 +368,12 @@ class DefaultWorkspaceManager {
const content = stringifyEnvironment(environment, { format: 'yml' });
await writeFile(envFilePath, content);
if (env.uid === activeGlobalEnvironmentUid && !workspaceConfig.activeEnvironmentUid) {
workspaceConfig.activeEnvironmentUid = generateUidBasedOnHash(envFilePath);
// Map the legacy active env uid to the new file-based uid in the per-workspace store
if (env.uid === activeGlobalEnvironmentUid) {
globalEnvironmentsStore.setActiveGlobalEnvironmentUidForWorkspace(
workspacePath,
generateUidBasedOnHash(envFilePath)
);
}
}
}

View File

@@ -2,6 +2,7 @@ const _ = require('lodash');
const Store = require('electron-store');
const { encryptStringSafe, decryptStringSafe } = require('../utils/encryption');
const { environmentSchema } = require('@usebruno/schema');
const { posixifyPath } = require('../utils/filesystem');
class GlobalEnvironmentsStore {
constructor() {
@@ -86,6 +87,36 @@ class GlobalEnvironmentsStore {
return this.store.get('activeGlobalEnvironmentUid', null);
}
setActiveGlobalEnvironmentUid(uid) {
return this.store.set('activeGlobalEnvironmentUid', uid);
}
getActiveGlobalEnvironmentUidForWorkspace(workspacePath) {
if (!workspacePath) return undefined;
const key = posixifyPath(workspacePath);
const mapping = this.store.get('activeGlobalEnvironmentUidByWorkspace', {});
if (key in mapping) {
return mapping[key];
}
return undefined;
}
setActiveGlobalEnvironmentUidForWorkspace(workspacePath, uid) {
if (!workspacePath) return;
const key = posixifyPath(workspacePath);
const mapping = this.store.get('activeGlobalEnvironmentUidByWorkspace', {});
mapping[key] = uid || null;
this.store.set('activeGlobalEnvironmentUidByWorkspace', mapping);
}
removeActiveGlobalEnvironmentUidForWorkspace(workspacePath) {
if (!workspacePath) return;
const key = posixifyPath(workspacePath);
const mapping = this.store.get('activeGlobalEnvironmentUidByWorkspace', {});
delete mapping[key];
this.store.set('activeGlobalEnvironmentUidByWorkspace', mapping);
}
setGlobalEnvironments(globalEnvironments) {
globalEnvironments = this.filterValidEnvironments(globalEnvironments);
@@ -93,10 +124,6 @@ class GlobalEnvironmentsStore {
return this.store.set('environments', globalEnvironments);
}
setActiveGlobalEnvironmentUid(uid) {
return this.store.set('activeGlobalEnvironmentUid', uid);
}
addGlobalEnvironment({ uid, name, variables = [], color }) {
let globalEnvironments = this.getGlobalEnvironments();
const existingEnvironment = globalEnvironments.find((env) => env?.name == name);

View File

@@ -1,18 +1,11 @@
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const yaml = require('js-yaml');
const { parseEnvironment, stringifyEnvironment } = require('@usebruno/filestore');
const { writeFile, createDirectory } = require('../utils/filesystem');
const { generateUidBasedOnHash, uuid } = require('../utils/common');
const { decryptStringSafe } = require('../utils/encryption');
const EnvironmentSecretsStore = require('./env-secrets');
const {
readWorkspaceConfig,
generateYamlContent,
writeWorkspaceFileAtomic
} = require('../utils/workspace-config');
const { withLock, getWorkspaceLockKey } = require('../utils/workspace-lock');
const environmentSecretsStore = new EnvironmentSecretsStore();
@@ -98,8 +91,7 @@ class GlobalEnvironmentsManager {
if (!fs.existsSync(environmentsDir)) {
return {
globalEnvironments: [],
activeGlobalEnvironmentUid: null
globalEnvironments: []
};
}
@@ -119,58 +111,14 @@ class GlobalEnvironmentsManager {
}
}
const activeGlobalEnvironmentUid = await this.getActiveGlobalEnvironmentUid(workspacePath);
return {
globalEnvironments: environments,
activeGlobalEnvironmentUid
globalEnvironments: environments
};
} catch (error) {
throw error;
}
}
async getActiveGlobalEnvironmentUid(workspacePath) {
try {
if (!workspacePath) {
return null;
}
const workspaceFilePath = path.join(workspacePath, 'workspace.yml');
if (!fs.existsSync(workspaceFilePath)) {
return null;
}
const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8');
const workspaceConfig = yaml.load(yamlContent);
return workspaceConfig.activeEnvironmentUid || null;
} catch (error) {
return null;
}
}
async setActiveGlobalEnvironmentUid(workspacePath, environmentUid) {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
const workspaceFilePath = path.join(workspacePath, 'workspace.yml');
if (!fs.existsSync(workspaceFilePath)) {
throw new Error('Invalid workspace: workspace.yml not found');
}
return withLock(getWorkspaceLockKey(workspacePath), async () => {
const workspaceConfig = readWorkspaceConfig(workspacePath);
workspaceConfig.activeEnvironmentUid = environmentUid;
const yamlOutput = generateYamlContent(workspaceConfig);
await writeWorkspaceFileAtomic(workspacePath, yamlOutput);
return true;
});
}
async createGlobalEnvironment(workspacePath, { uid, name, variables, color }) {
try {
if (!workspacePath) {
@@ -303,24 +251,6 @@ class GlobalEnvironmentsManager {
fs.unlinkSync(envFile.filePath);
const activeGlobalEnvironmentUid = await this.getActiveGlobalEnvironmentUid(workspacePath);
if (activeGlobalEnvironmentUid === environmentUid) {
await this.setActiveGlobalEnvironmentUid(workspacePath, null);
}
return true;
} catch (error) {
throw error;
}
}
async selectGlobalEnvironment(workspacePath, { environmentUid }) {
try {
if (!workspacePath) {
throw new Error('Workspace path is required');
}
await this.setActiveGlobalEnvironmentUid(workspacePath, environmentUid);
return true;
} catch (error) {
throw error;
@@ -371,10 +301,6 @@ class GlobalEnvironmentsManager {
return this.deleteGlobalEnvironment(workspacePath, params);
}
async selectGlobalEnvironmentByPath(workspacePath, params) {
return this.selectGlobalEnvironment(workspacePath, params);
}
async updateGlobalEnvironmentColorByPath(workspacePath, { environmentUid, color }) {
return this.updateGlobalEnvironmentColor(workspacePath, environmentUid, color);
}

View File

@@ -272,11 +272,6 @@ const generateYamlContent = (config) => {
yamlLines.push('docs: \'\'');
}
if (config.activeEnvironmentUid && typeof config.activeEnvironmentUid === 'string') {
yamlLines.push('');
yamlLines.push(`activeEnvironmentUid: ${config.activeEnvironmentUid}`);
}
yamlLines.push('');
return yamlLines.join('\n');