From ac0a71d8778173e886e371be05a2008241e00ae9 Mon Sep 17 00:00:00 2001 From: ravindra-bruno Date: Thu, 2 Jul 2026 15:01:14 +0530 Subject: [PATCH 1/5] Added the ignore for the collection nested folders --- .../Ignore/StyledWrapper.js | 87 +++++++++++++++++ .../CollectionSettings/Ignore/index.js | 89 +++++++++++++++++ .../components/CollectionSettings/index.js | 11 +++ .../IgnoreCollectionItem/index.js | 52 ++++++++++ .../Collection/CollectionItem/index.js | 17 +++- .../ReduxStore/slices/collections/actions.js | 48 ++++++++++ .../src/app/collection-watcher.js | 11 ++- packages/bruno-electron/src/ipc/collection.js | 95 ++++++++++++++----- 8 files changed, 379 insertions(+), 31 deletions(-) create mode 100644 packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js create mode 100644 packages/bruno-app/src/components/CollectionSettings/Ignore/index.js create mode 100644 packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js new file mode 100644 index 00000000000..7de5e97cb64 --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js @@ -0,0 +1,87 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + label { + color: ${(props) => props.theme.text}; + } + + .hint { + color: ${(props) => props.theme.colors.text.muted}; + } + + table { + width: 100%; + border-collapse: collapse; + + thead { + th { + text-align: left; + font-size: ${(props) => props.theme.font.size.xs}; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + color: ${(props) => props.theme.table.thead.color}; + border: 1px solid ${(props) => props.theme.table.border}; + padding: 0.5rem 0.75rem; + + &.text-right { + text-align: right; + } + } + } + + tbody { + td { + border: 1px solid ${(props) => props.theme.table.border}; + padding: 0.5rem 0.75rem; + + &.text-center { + text-align: center; + } + + &.text-right { + text-align: right; + } + } + } + } + + .folder-icon { + color: ${(props) => props.theme.colors.text.muted}; + } + + .folder-name { + font-weight: 500; + color: ${(props) => props.theme.text}; + } + + .path-text { + font-size: ${(props) => props.theme.font.size.xs}; + color: ${(props) => props.theme.colors.text.muted}; + font-family: monospace; + } + + .empty-state { + .empty-icon { + color: ${(props) => props.theme.colors.text.muted}; + } + + .empty-text { + color: ${(props) => props.theme.colors.text.muted}; + } + } + + .action-button { + padding: 0.25rem; + border-radius: ${(props) => props.theme.border.radius.base}; + transition: all 0.2s; + color: ${(props) => props.theme.colors.text.muted}; + + &:hover { + color: ${(props) => props.theme.text}; + background-color: ${(props) => props.theme.dropdown.hoverBg}; + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js b/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js new file mode 100644 index 00000000000..47f1361c34e --- /dev/null +++ b/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js @@ -0,0 +1,89 @@ +import React from 'react'; +import get from 'lodash/get'; +import { useDispatch } from 'react-redux'; +import { IconFolder, IconFolderOff, IconEye } from '@tabler/icons'; +import { getBasename } from 'utils/common/path'; +import { unignoreFolder } from 'providers/ReduxStore/slices/collections/actions'; +import toast from 'react-hot-toast'; +import StyledWrapper from './StyledWrapper'; + +const DEFAULT_IGNORES = ['node_modules', '.git']; + +const Ignore = ({ collection }) => { + const dispatch = useDispatch(); + + const ignore = get(collection, 'brunoConfig.ignore', []); + const ignoredFolders = ignore.filter((entry) => !DEFAULT_IGNORES.includes(entry)); + + const handleUnignore = (relativePath) => { + dispatch(unignoreFolder(relativePath, collection.uid)) + .then(() => toast.success('Folder unignored')) + .catch((error) => { + console.error(error); + toast.error('Failed to unignore folder'); + }); + }; + + return ( + +
+ +

+ Ignored folders are excluded from the collection. Right-click a folder in the sidebar and choose Ignore to add one here. +

+
+ + + + + + + + + + + {ignoredFolders.length === 0 ? ( + + + + ) : ( + ignoredFolders.map((relativePath, index) => ( + + + + + + )) + )} + +
FolderPathActions
+
+ + No ignored folders +
+
+
+ + {getBasename(collection.pathname, relativePath)} +
+
+
{relativePath}
+
+ +
+
+ ); +}; + +export default Ignore; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index 95adf02cced..54cd07d488e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -11,6 +11,7 @@ import Script from './Script'; import Test from './Tests'; import Presets from './Presets'; import Protobuf from './Protobuf'; +import Ignore from './Ignore'; import StyledWrapper from './StyledWrapper'; import Vars from './Vars/index'; import StatusDot from 'components/StatusDot'; @@ -60,6 +61,9 @@ const CollectionSettings = ({ collection }) => { const protobufConfig = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.protobuf', {}) : get(collection, 'brunoConfig.protobuf', {}); + const ignoredFoldersCount = get(collection, 'brunoConfig.ignore', []).filter( + (entry) => entry !== 'node_modules' && entry !== '.git' + ).length; const presets = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.presets', {}) : get(collection, 'brunoConfig.presets', {}); const hasPresets = presets && ((presets.requestType && presets.requestType !== DEFAULT_PRESET_REQUEST_TYPE) || (presets.requestUrl && presets.requestUrl !== '')); @@ -95,6 +99,9 @@ const CollectionSettings = ({ collection }) => { case 'protobuf': { return ; } + case 'ignore': { + return ; + } } }; @@ -146,6 +153,10 @@ const CollectionSettings = ({ collection }) => { Protobuf {protobufConfig.protoFiles && protobufConfig.protoFiles.length > 0 && } +
setTab('ignore')}> + Ignore + {ignoredFoldersCount > 0 && {ignoredFoldersCount}} +
{getTabPanel(tab)}
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js new file mode 100644 index 00000000000..3a6a966883e --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js @@ -0,0 +1,52 @@ +import React from 'react'; +import Modal from 'components/Modal'; +import { useSelector, useDispatch } from 'react-redux'; +import { ignoreFolder, closeTabs } from 'providers/ReduxStore/slices/collections/actions'; +import { recursivelyGetAllItemUids } from 'utils/collections'; +import toast from 'react-hot-toast'; + +const IgnoreCollectionItem = ({ onClose, item, collectionUid }) => { + const dispatch = useDispatch(); + const collection = useSelector((state) => state.collections.collections?.find((c) => c.uid === collectionUid)); + const isYamlCollection = collection?.format === 'yml' || Boolean(collection?.brunoConfig?.opencollection); + const configFileName = isYamlCollection ? 'opencollection.yml' : 'bruno.json'; + + const onConfirm = () => { + dispatch(ignoreFolder(item.uid, collectionUid)) + .then(() => { + const tabUids = [...recursivelyGetAllItemUids(item.items), item.uid]; + dispatch(closeTabs({ tabUids })); + toast.success('Folder ignored'); + }) + .catch((error) => { + console.error('Error ignoring folder', error); + toast.error(error?.message || 'Error ignoring folder'); + }); + onClose(); + }; + + return ( + + Ignoring {item.name} will hide it from this + {' '} + {isYamlCollection ? 'opencollection (YAML)' : 'Bruno (JSON)'} + {' '} + collection by adding it to the + {' '} + ignore + {' '} + list in + {' '} + {configFileName} + . The folder and its files are not deleted, and you can unignore it later from the collection settings. + + ); +}; + +export default IgnoreCollectionItem; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js index 89ecf54388d..487a67e60b9 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js @@ -19,7 +19,8 @@ import { IconSettings, IconInfoCircle, IconTerminal2, - IconAppWindow + IconAppWindow, + IconEyeOff } from '@tabler/icons'; import { useSelector, useDispatch } from 'react-redux'; import { addTab, focusTab, makeTabPermanent } from 'providers/ReduxStore/slices/tabs'; @@ -34,6 +35,7 @@ import NewApp from 'components/Sidebar/NewApp'; import RenameCollectionItem from './RenameCollectionItem'; import CloneCollectionItem from './CloneCollectionItem'; import DeleteCollectionItem from './DeleteCollectionItem'; +import IgnoreCollectionItem from './IgnoreCollectionItem'; import RunCollectionItem from './RunCollectionItem'; import GenerateCodeItem from './GenerateCodeItem'; import { isItemARequest, isItemAFolder } from 'utils/tabs'; @@ -93,6 +95,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) const [renameItemModalOpen, setRenameItemModalOpen] = useState(false); const [cloneItemModalOpen, setCloneItemModalOpen] = useState(false); const [deleteItemModalOpen, setDeleteItemModalOpen] = useState(false); + const [ignoreItemModalOpen, setIgnoreItemModalOpen] = useState(false); const [createExampleModalOpen, setCreateExampleModalOpen] = useState(false); const [generateCodeItemModalOpen, setGenerateCodeItemModalOpen] = useState(false); const [newRequestModalOpen, setNewRequestModalOpen] = useState(false); @@ -457,6 +460,15 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) } ); + if (isFolder) { + items.push({ + id: 'ignore', + leftSection: IconEyeOff, + label: 'Ignore', + onClick: () => setIgnoreItemModalOpen(true) + }); + } + items.push({ id: 'separator-1', type: 'divider' }); items.push({ @@ -654,6 +666,9 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText }) {deleteItemModalOpen && ( setDeleteItemModalOpen(false)} /> )} + {ignoreItemModalOpen && ( + setIgnoreItemModalOpen(false)} /> + )} {newRequestModalOpen && ( setNewRequestModalOpen(false)} /> )} diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 8051faff5b2..44a5e3a59de 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -38,6 +38,8 @@ import { sortCollections as _sortCollections, updateCollectionMountStatus, moveCollection, + deleteItem as _deleteItemFromState, + brunoConfigUpdateEvent as _brunoConfigUpdateEvent, workspaceEnvUpdateEvent, requestCancelled, resetRunResults, @@ -2744,6 +2746,52 @@ export const updateBrunoConfig = (brunoConfig, collectionUid) => (dispatch, getS }); }; +export const ignoreFolder = (itemUid, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + const item = findItemInCollection(collection, itemUid); + if (!item) { + return reject(new Error('Unable to locate item')); + } + + const { ipcRenderer } = window; + ipcRenderer + .invoke('renderer:ignore-folder', collectionUid, collection.pathname, collection.root, collection.brunoConfig || {}, item.pathname) + .then((updatedBrunoConfig) => { + dispatch(_brunoConfigUpdateEvent({ collectionUid, brunoConfig: updatedBrunoConfig })); + dispatch(_deleteItemFromState({ itemUid, collectionUid })); + resolve(); + }) + .catch(reject); + }); +}; + +export const unignoreFolder = (relativePath, collectionUid) => (dispatch, getState) => { + const state = getState(); + const collection = findCollectionByUid(state.collections.collections, collectionUid); + + return new Promise((resolve, reject) => { + if (!collection) { + return reject(new Error('Collection not found')); + } + + const { ipcRenderer } = window; + ipcRenderer + .invoke('renderer:unignore-folder', collectionUid, collection.pathname, collection.root, collection.brunoConfig || {}, relativePath) + .then((updatedBrunoConfig) => { + dispatch(_brunoConfigUpdateEvent({ collectionUid, brunoConfig: updatedBrunoConfig })); + resolve(); + }) + .catch(reject); + }); +}; + /** * Opens a scratch collection and creates it in Redux state. * This is a simplified version of openCollectionEvent for scratch collections, diff --git a/packages/bruno-electron/src/app/collection-watcher.js b/packages/bruno-electron/src/app/collection-watcher.js index a7d79b483f5..32ed10f7386 100644 --- a/packages/bruno-electron/src/app/collection-watcher.js +++ b/packages/bruno-electron/src/app/collection-watcher.js @@ -21,7 +21,7 @@ const { uuid } = require('../utils/common'); const { parseValueByDataType } = require('@usebruno/common/utils'); const { getRequestUid } = require('../cache/requestUids'); const { decryptStringSafe } = require('../utils/encryption'); -const { setBrunoConfig } = require('../store/bruno-config'); +const { setBrunoConfig, getBrunoConfig } = require('../store/bruno-config'); const EnvironmentSecretsStore = require('../store/env-secrets'); const snapshotManager = require('../services/snapshot'); const { parseFileMeta, hydrateRequestWithUuid } = require('../utils/collection'); @@ -811,8 +811,6 @@ class CollectionWatcher { // Always ignore node_modules and .git, regardless of user config // This prevents infinite loops with symlinked directories (e.g., npm workspaces) const defaultIgnores = ['node_modules', '.git']; - const userIgnores = brunoConfig?.ignore || []; - const ignores = [...new Set([...defaultIgnores, ...userIgnores])]; setTimeout(() => { const watcher = chokidar.watch(watchPath, { @@ -834,8 +832,13 @@ class CollectionWatcher { return true; } + const userIgnores = getBrunoConfig(collectionUid)?.ignore || []; + const ignores = [...new Set([...defaultIgnores, ...userIgnores])]; + const normalizedRelativePath = relativePath.split(path.sep).join('/'); + return ignores.some((ignorePattern) => { - return relativePath === ignorePattern || relativePath.startsWith(ignorePattern); + const normalizedIgnorePattern = ignorePattern.replace(/\\/g, '/'); + return normalizedRelativePath === normalizedIgnorePattern || normalizedRelativePath.startsWith(normalizedIgnorePattern); }); }, persistent: true, diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index fa09186b893..bf0e5b299db 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -74,6 +74,7 @@ const interpolateVars = require('./network/interpolate-vars'); const { interpolateString } = require('./network/interpolate-string'); const { getEnvVars, getTreePathFromCollectionToItem, mergeVars, parseBruFileMeta, hydrateRequestWithUuid, transformRequestToSaveToFilesystem } = require('../utils/collection'); const { getProcessEnvVars } = require('../store/process-env'); +const { setBrunoConfig } = require('../store/bruno-config'); const { getOAuth2TokenUsingAuthorizationCode, getOAuth2TokenUsingClientCredentials, getOAuth2TokenUsingPasswordCredentials, getOAuth2TokenUsingImplicitGrant, refreshOauth2Token } = require('../utils/oauth2'); const { getCertsAndProxyConfig } = require('./network/cert-utils'); const collectionWatcher = require('../app/collection-watcher'); @@ -1708,36 +1709,78 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { } }); - ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionRoot) => { - try { - const transformedBrunoConfig = transformBrunoConfigBeforeSave(brunoConfig); - const format = getCollectionFormat(collectionPath); + const writeBrunoConfig = async (brunoConfig, collectionPath, collectionRoot) => { + const transformedBrunoConfig = transformBrunoConfigBeforeSave(brunoConfig); + const format = getCollectionFormat(collectionPath); - if (format === 'bru') { - const brunoConfigPath = path.join(collectionPath, 'bruno.json'); - const content = await stringifyJson(transformedBrunoConfig); - await writeFile(brunoConfigPath, content); - } else if (format === 'yml') { - // opencollection.yml holds both config AND the collection root. If the caller - // didn't supply a root (e.g. a config-only update before the tree finished - // loading), recover it from disk so request defaults/docs/scripts aren't wiped. - let rootToWrite = collectionRoot; - if (!rootToWrite) { - const ocYmlPath = path.join(collectionPath, 'opencollection.yml'); - if (fs.existsSync(ocYmlPath)) { - try { - const existing = fs.readFileSync(ocYmlPath, 'utf8'); - rootToWrite = parseCollection(existing, { format }).collectionRoot; - } catch (e) { - rootToWrite = collectionRoot; - } + if (format === 'bru') { + const brunoConfigPath = path.join(collectionPath, 'bruno.json'); + const content = await stringifyJson(transformedBrunoConfig); + await writeFile(brunoConfigPath, content); + } else if (format === 'yml') { + // opencollection.yml holds both config AND the collection root. If the caller + // didn't supply a root (e.g. a config-only update before the tree finished + // loading), recover it from disk so request defaults/docs/scripts aren't wiped. + let rootToWrite = collectionRoot; + if (!rootToWrite) { + const ocYmlPath = path.join(collectionPath, 'opencollection.yml'); + if (fs.existsSync(ocYmlPath)) { + try { + const existing = fs.readFileSync(ocYmlPath, 'utf8'); + rootToWrite = parseCollection(existing, { format }).collectionRoot; + } catch (e) { + rootToWrite = collectionRoot; } } - const content = await stringifyCollection(rootToWrite, transformedBrunoConfig, { format }); - await writeFile(path.join(collectionPath, 'opencollection.yml'), content); - } else { - throw new Error(`Invalid collection format: ${format}`); } + const content = await stringifyCollection(rootToWrite, transformedBrunoConfig, { format }); + await writeFile(path.join(collectionPath, 'opencollection.yml'), content); + } else { + throw new Error(`Invalid collection format: ${format}`); + } + }; + + ipcMain.handle('renderer:update-bruno-config', async (event, brunoConfig, collectionPath, collectionRoot) => { + try { + await writeBrunoConfig(brunoConfig, collectionPath, collectionRoot); + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:ignore-folder', async (event, collectionUid, collectionPath, collectionRoot, brunoConfig, folderPath) => { + try { + const relativePath = path.relative(collectionPath, folderPath).replace(/\\/g, '/'); + const existingIgnores = brunoConfig?.ignore || []; + const updatedBrunoConfig = { + ...brunoConfig, + ignore: [...new Set([...existingIgnores, relativePath])] + }; + + await writeBrunoConfig(updatedBrunoConfig, collectionPath, collectionRoot); + setBrunoConfig(collectionUid, updatedBrunoConfig); + collectionWatcher.unlinkItemPathInWatcher(folderPath); + + return updatedBrunoConfig; + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:unignore-folder', async (event, collectionUid, collectionPath, collectionRoot, brunoConfig, relativePath) => { + try { + const normalizedPath = relativePath.replace(/\\/g, '/'); + const existingIgnores = brunoConfig?.ignore || []; + const updatedBrunoConfig = { + ...brunoConfig, + ignore: existingIgnores.filter((p) => p.replace(/\\/g, '/') !== normalizedPath) + }; + + await writeBrunoConfig(updatedBrunoConfig, collectionPath, collectionRoot); + setBrunoConfig(collectionUid, updatedBrunoConfig); + collectionWatcher.addItemPathInWatcher(path.resolve(collectionPath, relativePath)); + + return updatedBrunoConfig; } catch (error) { return Promise.reject(error); } From 04839cf6d7f6f12040888d4120f83059b64ed40c Mon Sep 17 00:00:00 2001 From: ravindra-bruno Date: Thu, 2 Jul 2026 16:26:07 +0530 Subject: [PATCH 2/5] Removed the unignored functionality for Collection folder for now --- .../Ignore/StyledWrapper.js | 87 ------------------ .../CollectionSettings/Ignore/index.js | 89 ------------------- .../components/CollectionSettings/index.js | 11 --- .../IgnoreCollectionItem/index.js | 9 +- .../ReduxStore/slices/collections/actions.js | 20 ----- packages/bruno-electron/src/ipc/collection.js | 19 ---- 6 files changed, 8 insertions(+), 227 deletions(-) delete mode 100644 packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js delete mode 100644 packages/bruno-app/src/components/CollectionSettings/Ignore/index.js diff --git a/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js b/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js deleted file mode 100644 index 7de5e97cb64..00000000000 --- a/packages/bruno-app/src/components/CollectionSettings/Ignore/StyledWrapper.js +++ /dev/null @@ -1,87 +0,0 @@ -import styled from 'styled-components'; - -const StyledWrapper = styled.div` - label { - color: ${(props) => props.theme.text}; - } - - .hint { - color: ${(props) => props.theme.colors.text.muted}; - } - - table { - width: 100%; - border-collapse: collapse; - - thead { - th { - text-align: left; - font-size: ${(props) => props.theme.font.size.xs}; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.05em; - color: ${(props) => props.theme.table.thead.color}; - border: 1px solid ${(props) => props.theme.table.border}; - padding: 0.5rem 0.75rem; - - &.text-right { - text-align: right; - } - } - } - - tbody { - td { - border: 1px solid ${(props) => props.theme.table.border}; - padding: 0.5rem 0.75rem; - - &.text-center { - text-align: center; - } - - &.text-right { - text-align: right; - } - } - } - } - - .folder-icon { - color: ${(props) => props.theme.colors.text.muted}; - } - - .folder-name { - font-weight: 500; - color: ${(props) => props.theme.text}; - } - - .path-text { - font-size: ${(props) => props.theme.font.size.xs}; - color: ${(props) => props.theme.colors.text.muted}; - font-family: monospace; - } - - .empty-state { - .empty-icon { - color: ${(props) => props.theme.colors.text.muted}; - } - - .empty-text { - color: ${(props) => props.theme.colors.text.muted}; - } - } - - .action-button { - padding: 0.25rem; - border-radius: ${(props) => props.theme.border.radius.base}; - transition: all 0.2s; - color: ${(props) => props.theme.colors.text.muted}; - - &:hover { - color: ${(props) => props.theme.text}; - background-color: ${(props) => props.theme.dropdown.hoverBg}; - } - } -`; - -export default StyledWrapper; diff --git a/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js b/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js deleted file mode 100644 index 47f1361c34e..00000000000 --- a/packages/bruno-app/src/components/CollectionSettings/Ignore/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import get from 'lodash/get'; -import { useDispatch } from 'react-redux'; -import { IconFolder, IconFolderOff, IconEye } from '@tabler/icons'; -import { getBasename } from 'utils/common/path'; -import { unignoreFolder } from 'providers/ReduxStore/slices/collections/actions'; -import toast from 'react-hot-toast'; -import StyledWrapper from './StyledWrapper'; - -const DEFAULT_IGNORES = ['node_modules', '.git']; - -const Ignore = ({ collection }) => { - const dispatch = useDispatch(); - - const ignore = get(collection, 'brunoConfig.ignore', []); - const ignoredFolders = ignore.filter((entry) => !DEFAULT_IGNORES.includes(entry)); - - const handleUnignore = (relativePath) => { - dispatch(unignoreFolder(relativePath, collection.uid)) - .then(() => toast.success('Folder unignored')) - .catch((error) => { - console.error(error); - toast.error('Failed to unignore folder'); - }); - }; - - return ( - -
- -

- Ignored folders are excluded from the collection. Right-click a folder in the sidebar and choose Ignore to add one here. -

-
- - - - - - - - - - - {ignoredFolders.length === 0 ? ( - - - - ) : ( - ignoredFolders.map((relativePath, index) => ( - - - - - - )) - )} - -
FolderPathActions
-
- - No ignored folders -
-
-
- - {getBasename(collection.pathname, relativePath)} -
-
-
{relativePath}
-
- -
-
- ); -}; - -export default Ignore; diff --git a/packages/bruno-app/src/components/CollectionSettings/index.js b/packages/bruno-app/src/components/CollectionSettings/index.js index 54cd07d488e..95adf02cced 100644 --- a/packages/bruno-app/src/components/CollectionSettings/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/index.js @@ -11,7 +11,6 @@ import Script from './Script'; import Test from './Tests'; import Presets from './Presets'; import Protobuf from './Protobuf'; -import Ignore from './Ignore'; import StyledWrapper from './StyledWrapper'; import Vars from './Vars/index'; import StatusDot from 'components/StatusDot'; @@ -61,9 +60,6 @@ const CollectionSettings = ({ collection }) => { const protobufConfig = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.protobuf', {}) : get(collection, 'brunoConfig.protobuf', {}); - const ignoredFoldersCount = get(collection, 'brunoConfig.ignore', []).filter( - (entry) => entry !== 'node_modules' && entry !== '.git' - ).length; const presets = collection.draft?.brunoConfig ? get(collection, 'draft.brunoConfig.presets', {}) : get(collection, 'brunoConfig.presets', {}); const hasPresets = presets && ((presets.requestType && presets.requestType !== DEFAULT_PRESET_REQUEST_TYPE) || (presets.requestUrl && presets.requestUrl !== '')); @@ -99,9 +95,6 @@ const CollectionSettings = ({ collection }) => { case 'protobuf': { return ; } - case 'ignore': { - return ; - } } }; @@ -153,10 +146,6 @@ const CollectionSettings = ({ collection }) => { Protobuf {protobufConfig.protoFiles && protobufConfig.protoFiles.length > 0 && } -
setTab('ignore')}> - Ignore - {ignoredFoldersCount > 0 && {ignoredFoldersCount}} -
{getTabPanel(tab)}
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js index 3a6a966883e..c07af7053f5 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/IgnoreCollectionItem/index.js @@ -44,7 +44,14 @@ const IgnoreCollectionItem = ({ onClose, item, collectionUid }) => { list in {' '} {configFileName} - . The folder and its files are not deleted, and you can unignore it later from the collection settings. + . The folder and its files are not deleted. To restore it later, remove the entry from the + {' '} + ignore + {' '} + list in + {' '} + {configFileName} + . ); }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js index 44a5e3a59de..d48a405cecf 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js @@ -2772,26 +2772,6 @@ export const ignoreFolder = (itemUid, collectionUid) => (dispatch, getState) => }); }; -export const unignoreFolder = (relativePath, collectionUid) => (dispatch, getState) => { - const state = getState(); - const collection = findCollectionByUid(state.collections.collections, collectionUid); - - return new Promise((resolve, reject) => { - if (!collection) { - return reject(new Error('Collection not found')); - } - - const { ipcRenderer } = window; - ipcRenderer - .invoke('renderer:unignore-folder', collectionUid, collection.pathname, collection.root, collection.brunoConfig || {}, relativePath) - .then((updatedBrunoConfig) => { - dispatch(_brunoConfigUpdateEvent({ collectionUid, brunoConfig: updatedBrunoConfig })); - resolve(); - }) - .catch(reject); - }); -}; - /** * Opens a scratch collection and creates it in Redux state. * This is a simplified version of openCollectionEvent for scratch collections, diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index bf0e5b299db..ca5ff831c18 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -1767,25 +1767,6 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { } }); - ipcMain.handle('renderer:unignore-folder', async (event, collectionUid, collectionPath, collectionRoot, brunoConfig, relativePath) => { - try { - const normalizedPath = relativePath.replace(/\\/g, '/'); - const existingIgnores = brunoConfig?.ignore || []; - const updatedBrunoConfig = { - ...brunoConfig, - ignore: existingIgnores.filter((p) => p.replace(/\\/g, '/') !== normalizedPath) - }; - - await writeBrunoConfig(updatedBrunoConfig, collectionPath, collectionRoot); - setBrunoConfig(collectionUid, updatedBrunoConfig); - collectionWatcher.addItemPathInWatcher(path.resolve(collectionPath, relativePath)); - - return updatedBrunoConfig; - } catch (error) { - return Promise.reject(error); - } - }); - ipcMain.handle('renderer:open-devtools', async () => { mainWindow.webContents.openDevTools(); }); From 4227c2686ace5f12370cd50e12c233589d4e9b67 Mon Sep 17 00:00:00 2001 From: ravindra-bruno Date: Thu, 16 Jul 2026 16:18:12 +0530 Subject: [PATCH 3/5] require path boundary in folder ignore matching to prevent prefix and empty-pattern over-matching --- packages/bruno-electron/src/app/collection-watcher.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/app/collection-watcher.js b/packages/bruno-electron/src/app/collection-watcher.js index 32ed10f7386..0d62c03951f 100644 --- a/packages/bruno-electron/src/app/collection-watcher.js +++ b/packages/bruno-electron/src/app/collection-watcher.js @@ -838,7 +838,10 @@ class CollectionWatcher { return ignores.some((ignorePattern) => { const normalizedIgnorePattern = ignorePattern.replace(/\\/g, '/'); - return normalizedRelativePath === normalizedIgnorePattern || normalizedRelativePath.startsWith(normalizedIgnorePattern); + if (!normalizedIgnorePattern) { + return false; + } + return normalizedRelativePath === normalizedIgnorePattern || normalizedRelativePath.startsWith(`${normalizedIgnorePattern}/`); }); }, persistent: true, From 76f235b89fab8f65e1426fe4032af97ec30ce42e Mon Sep 17 00:00:00 2001 From: ravindra-bruno Date: Tue, 28 Jul 2026 14:57:44 +0530 Subject: [PATCH 4/5] fix(collection): deep-cloned bruno config before transform so ignore/update never mutated the object returned to redux --- packages/bruno-electron/src/ipc/collection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ca5ff831c18..6708a1924a2 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -1710,7 +1710,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { }); const writeBrunoConfig = async (brunoConfig, collectionPath, collectionRoot) => { - const transformedBrunoConfig = transformBrunoConfigBeforeSave(brunoConfig); + const transformedBrunoConfig = transformBrunoConfigBeforeSave(_.cloneDeep(brunoConfig)); const format = getCollectionFormat(collectionPath); if (format === 'bru') { From 0357da679f784d07d0d43b0d8f61a3898540a5e0 Mon Sep 17 00:00:00 2001 From: ravindra-bruno Date: Tue, 28 Jul 2026 17:16:34 +0530 Subject: [PATCH 5/5] resolved the coderebbit review comments --- packages/bruno-electron/src/ipc/collection.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 2e43026ee32..f9d57f2fbde 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -1719,12 +1719,8 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { if (!rootToWrite) { const ocYmlPath = path.join(collectionPath, 'opencollection.yml'); if (fs.existsSync(ocYmlPath)) { - try { - const existing = fs.readFileSync(ocYmlPath, 'utf8'); - rootToWrite = parseCollection(existing, { format }).collectionRoot; - } catch (e) { - rootToWrite = collectionRoot; - } + const existing = fs.readFileSync(ocYmlPath, 'utf8'); + rootToWrite = parseCollection(existing, { format }).collectionRoot; } } const content = await stringifyCollection(rootToWrite, transformedBrunoConfig, { format });