Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ export const removeCollectionFromWorkspaceAction = (workspaceUid, collectionPath
(c) => normalizePath(c.pathname) === normalizedCollectionPath
);

// Remove collection from the watcher
// When deleting files, stop the collection watcher first to prevent
// chokidar from firing events on a directory that's being removed
if (deleteFiles && collection) {
const workspaceId = workspace.pathname || workspace.uid || 'default';
await ipcRenderer.invoke('renderer:remove-collection', collection.pathname, collection.uid, workspaceId);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Pre-removal IPC can prevent deleteFiles from deleting the collection directory.

renderer:remove-collection already removes the collection from workspace.yml. When Line 169 then calls renderer:remove-collection-from-workspace, removedCollection can be null, so the deleteFiles branch may never remove the folder.

Suggested fix (keep watcher teardown, avoid first workspace mutation)
-      if (deleteFiles && collection) {
-        const workspaceId = workspace.pathname || workspace.uid || 'default';
-        await ipcRenderer.invoke('renderer:remove-collection', collection.pathname, collection.uid, workspaceId);
-      }
+      if (deleteFiles && collection) {
+        // teardown watcher/runtime state only; let remove-collection-from-workspace
+        // own workspace.yml mutation + file deletion
+        await ipcRenderer.invoke('renderer:remove-collection', collection.pathname, collection.uid);
+      }

Also applies to: 169-173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js`
around lines 161 - 167, The current flow calls
ipcRenderer.invoke('renderer:remove-collection', ...) which mutates
workspace.yml before you stop the watcher, causing removedCollection to become
null and preventing deleteFiles from removing the folder; change the sequence so
you first tear down the watcher without mutating the workspace (invoke the
watcher-stop IPC — use 'renderer:remove-collection-from-workspace' or a
dedicated watcher-stop IPC) using collection.pathname/collection.uid/workspaceId
to stop chokidar, then call ipcRenderer.invoke('renderer:remove-collection',
collection.pathname, collection.uid, workspaceId) to mutate workspace.yml and
finally run the deleteFiles branch (or rely on the returned removedCollection)
so the directory removal actually executes.


await ipcRenderer.invoke('renderer:remove-collection-from-workspace',
workspaceUid,
workspace.pathname,
Expand Down
86 changes: 47 additions & 39 deletions packages/bruno-electron/src/app/collection-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,58 +552,66 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
};

const unlink = (win, pathname, collectionUid, collectionPath) => {
console.log(`watcher unlink: ${pathname}`);

if (isEnvironmentsFolder(pathname, collectionPath)) {
return unlinkEnvironmentFile(win, pathname, collectionUid);
}

const format = getCollectionFormat(collectionPath);
if (hasRequestExtension(pathname, format)) {
const basename = path.basename(pathname);
const dirname = path.dirname(pathname);
try {
console.log(`watcher unlink: ${pathname}`);

if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) {
return;
if (isEnvironmentsFolder(pathname, collectionPath)) {
return unlinkEnvironmentFile(win, pathname, collectionUid);
}

const file = {
meta: {
collectionUid,
pathname,
name: basename
const format = getCollectionFormat(collectionPath);
if (hasRequestExtension(pathname, format)) {
const basename = path.basename(pathname);
const dirname = path.dirname(pathname);

if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) {
return;
}
};
win.webContents.send('main:collection-tree-updated', 'unlink', file);

const file = {
meta: {
collectionUid,
pathname,
name: basename
}
};
win.webContents.send('main:collection-tree-updated', 'unlink', file);
}
} catch (err) {
console.error(`Error processing unlink event for: ${pathname}`, err);
}
};

const unlinkDir = async (win, pathname, collectionUid, collectionPath) => {
const envDirectory = path.join(collectionPath, 'environments');

if (path.normalize(pathname) === path.normalize(envDirectory)) {
return;
}
try {
const envDirectory = path.join(collectionPath, 'environments');

const format = getCollectionFormat(collectionPath);
const folderFilePath = path.join(pathname, `folder.${format}`);
if (path.normalize(pathname) === path.normalize(envDirectory)) {
return;
}

let name = path.basename(pathname);
const format = getCollectionFormat(collectionPath);
const folderFilePath = path.join(pathname, `folder.${format}`);

if (fs.existsSync(folderFilePath)) {
let folderFileContent = fs.readFileSync(folderFilePath, 'utf8');
let folderData = await parseFolder(folderFileContent, { format });
name = folderData?.meta?.name || name;
}
let name = path.basename(pathname);

const directory = {
meta: {
collectionUid,
pathname,
name
if (fs.existsSync(folderFilePath)) {
let folderFileContent = fs.readFileSync(folderFilePath, 'utf8');
let folderData = await parseFolder(folderFileContent, { format });
name = folderData?.meta?.name || name;
}
};
win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory);

const directory = {
meta: {
collectionUid,
pathname,
name
}
};
win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory);
} catch (err) {
console.error(`Error processing unlinkDir event for: ${pathname}`, err);
}
};

const onWatcherSetupComplete = (win, watchPath, collectionUid, watcher) => {
Expand Down
Loading