Skip to content

add: export & import of workspace as zip#6432

Merged
bijin-bruno merged 3 commits intousebruno:mainfrom
naman-bruno:add/export-workspace
Dec 17, 2025
Merged

add: export & import of workspace as zip#6432
bijin-bruno merged 3 commits intousebruno:mainfrom
naman-bruno:add/export-workspace

Conversation

@naman-bruno
Copy link
Collaborator

@naman-bruno naman-bruno commented Dec 17, 2025

Description

JIRA

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features
    • Export workspaces as ZIP files for backup or sharing, with success/error notifications.
    • Import workspaces from ZIP via drag-and-drop or file browser, including a modal UI and location selection, with success/error notifications.
    • Added workspace menu items to trigger import and export actions from the app UI.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 17, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

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

Walkthrough

Adds workspace import/export end-to-end: new ImportWorkspace UI, export action in workspace menu, Redux actions to call IPC, and two Electron IPC handlers (renderer:export-workspace, renderer:import-workspace). Introduces archiver and extract-zip dependencies.

Changes

Cohort / File(s) Summary
UI — App & Workspace menus
packages/bruno-app/src/components/AppTitleBar/index.js, packages/bruno-app/src/components/WorkspaceHome/index.js
Added menu entries and handlers for "Import workspace" (opens ImportWorkspace modal) and "Export" (dispatches export action and shows success/error toasts).
UI — Import modal
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js
New ImportWorkspace React component: modal with drag/drop and file picker for ZIP, validation, browse directory integration, submit flow that dispatches importWorkspaceAction, and success/error toasts.
State — Redux actions
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
Added exportWorkspaceAction(workspaceUid) and importWorkspaceAction(zipFilePath, extractLocation) which invoke corresponding IPC handlers and coordinate workspace creation/switching on import.
Electron IPC
packages/bruno-electron/src/ipc/workspace.js
Added IPC handlers renderer:export-workspace (creates ZIP, prompts save, excludes node_modules/.git) and renderer:import-workspace (extracts ZIP, detects workspace dir, validates workspace.yml, moves/registers workspace).
Dependencies
packages/bruno-electron/package.json
Added archiver (^7.0.1) and extract-zip (^2.0.1) to support ZIP creation and extraction.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as AppTitleBar / WorkspaceHome
    participant Redux as Redux Store
    participant IPC as Electron IPC
    participant FS as File System
    participant Toast as Toast Notifier

    User->>UI: Click "Export Workspace"
    UI->>Redux: dispatch exportWorkspaceAction(workspaceUid)
    Redux->>IPC: ipcRenderer.invoke('renderer:export-workspace', workspacePath, name)
    IPC->>FS: showSaveDialog
    FS-->>IPC: filePath / canceled
    alt canceled
        IPC-->>Redux: { canceled: true }
        Redux-->>UI: resolves
    else saved
        IPC->>FS: create ZIP (exclude node_modules, .git) via archiver
        FS-->>IPC: ZIP created at filePath
        IPC-->>Redux: { success: true, filePath }
        Redux-->>UI: resolves
        UI->>Toast: Show success message
    end
Loading
sequenceDiagram
    participant User
    participant UI as ImportWorkspace Modal
    participant Redux as Redux Store
    participant IPC as Electron IPC
    participant FS as File System
    participant WS as Workspace Manager
    participant Toast as Toast Notifier

    User->>UI: Select or drop ZIP file
    UI->>UI: validate ZIP
    User->>UI: Choose extract location (Browse -> dispatch browseDirectory)
    User->>UI: Submit import
    UI->>Redux: dispatch importWorkspaceAction(zipPath, location)
    Redux->>IPC: ipcRenderer.invoke('renderer:import-workspace', zipPath, location)
    IPC->>FS: extract-zip -> extract to temp dir
    IPC->>FS: detect workspace directory, read workspace.yml
    IPC->>FS: move/rename to final unique path
    alt success
        IPC->>WS: register watchers / open workspace
        IPC-->>Redux: { success: true, config, name }
        Redux->>Redux: dispatch createWorkspace(config) & switch active workspace
        UI->>Toast: Show success
        UI->>UI: Close modal
    else failure
        IPC-->>Redux: { success: false, error }
        UI->>Toast: Show error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay attention to: packages/bruno-electron/src/ipc/workspace.js (ZIP handling, temp dir cleanup, edge-case detection of workspace folder and workspace.yml parsing).
  • Review ImportWorkspace form/file handling for race conditions, IPC error surfaces, and path validation.
  • Verify Redux actions handle canceled IPC responses and propagate errors correctly.

Possibly related PRs

Suggested reviewers

  • lohit-bruno
  • helloanoop
  • bijin-bruno

Poem

✨ A zip arrives, a modal sings,
Files unpacked and workspace springs,
Export, import — bridges span,
Code and folders hand in hand. 📦🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add: export & import of workspace as zip' directly and clearly summarizes the primary functionality added in this changeset across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/bruno-electron/src/ipc/workspace.js (1)

344-348: Add safeguard against infinite loop.

While unlikely in practice, add a maximum iteration limit for defensive coding:

         let finalWorkspacePath = path.join(extractLocation, sanitizedName);
         let counter = 1;
-        while (fs.existsSync(finalWorkspacePath)) {
+        const maxAttempts = 1000;
+        while (fs.existsSync(finalWorkspacePath) && counter <= maxAttempts) {
           finalWorkspacePath = path.join(extractLocation, `${sanitizedName} (${counter})`);
           counter++;
         }
+        if (counter > maxAttempts) {
+          throw new Error('Unable to find available workspace name');
+        }
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (2)

105-116: Inconsistent promise handling style.

This function uses .then()/.catch() while onSubmit at line 30 uses async/await. Consider using async/await here for consistency.

-  const browse = () => {
-    dispatch(browseDirectory())
-      .then((dirPath) => {
-        if (typeof dirPath === 'string' && dirPath.length > 0) {
-          formik.setFieldValue('workspaceLocation', dirPath);
-        }
-      })
-      .catch((error) => {
-        formik.setFieldValue('workspaceLocation', '');
-        console.error(error);
-      });
+  const browse = async () => {
+    try {
+      const dirPath = await dispatch(browseDirectory());
+      if (typeof dirPath === 'string' && dirPath.length > 0) {
+        formik.setFieldValue('workspaceLocation', dirPath);
+      }
+    } catch (error) {
+      formik.setFieldValue('workspaceLocation', '');
+      console.error(error);
+    }
   };

125-129: Unnecessary ref in dependency array.

locationInputRef is a ref object that doesn't change between renders. Use an empty dependency array since this effect only needs to run on mount.

   useEffect(() => {
     if (locationInputRef && locationInputRef.current) {
       locationInputRef.current.focus();
     }
-  }, [locationInputRef]);
+  }, []);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 639c8e5 and cf89d82.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • packages/bruno-app/src/components/AppTitleBar/index.js (6 hunks)
  • packages/bruno-app/src/components/WorkspaceHome/index.js (3 hunks)
  • packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (1 hunks)
  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (1 hunks)
  • packages/bruno-electron/package.json (2 hunks)
  • packages/bruno-electron/src/ipc/workspace.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js
  • packages/bruno-app/src/components/AppTitleBar/index.js
  • packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js
  • packages/bruno-electron/src/ipc/workspace.js
  • packages/bruno-app/src/components/WorkspaceHome/index.js
🧬 Code graph analysis (4)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3)
packages/bruno-electron/src/app/apiSpecs.js (1)
  • workspaceUid (78-78)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/index.js (6)
  • workspaceUid (32-32)
  • workspace (20-20)
  • workspace (42-42)
  • workspace (50-50)
  • workspace (63-63)
  • workspace (75-75)
packages/bruno-app/src/utils/common/path.js (2)
  • result (83-83)
  • result (162-162)
packages/bruno-app/src/components/AppTitleBar/index.js (1)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (1)
  • ImportWorkspace (14-238)
packages/bruno-electron/src/ipc/workspace.js (2)
packages/bruno-electron/src/utils/workspace-config.js (11)
  • require (4-4)
  • require (5-5)
  • require (359-359)
  • fs (1-1)
  • path (2-2)
  • workspaceConfig (94-94)
  • yaml (3-3)
  • validateWorkspacePath (41-56)
  • readWorkspaceConfig (86-101)
  • validateWorkspaceConfig (166-182)
  • getWorkspaceUid (358-365)
packages/bruno-electron/src/utils/filesystem.js (3)
  • fs (2-2)
  • sanitizeName (180-187)
  • path (1-1)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (2)
  • exportWorkspaceAction (698-723)
  • exportWorkspaceAction (698-723)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Unit Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (18)
packages/bruno-app/src/components/WorkspaceHome/index.js (3)

3-4: LGTM!

Imports are correctly added for the new export functionality.


77-88: LGTM!

Handler follows the established pattern: hides dropdown, dispatches action, handles success/cancel/error with appropriate toast messages. Good use of optional chaining for error handling.


230-233: LGTM!

Export menu item correctly wired with consistent icon sizing and handler binding.

packages/bruno-app/src/components/AppTitleBar/index.js (5)

2-2: LGTM!

Import additions are appropriate for the new functionality.

Also applies to: 17-17


60-60: LGTM!

State management follows the existing createWorkspaceModalOpen pattern.


93-95: LGTM!

Simple and consistent with handleCreateWorkspace.


163-169: LGTM!

Menu item properly added with correct icon and handler.


180-182: LGTM!

Modal rendering follows the established pattern for CreateWorkspace.

packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (2)

698-722: LGTM!

Solid validation and error handling. Correctly checks workspace existence and pathname before invoking IPC.


725-744: The createWorkspace reducer already handles idempotent additions. It checks for an existing workspace by uid and merges the data using Object.assign instead of creating a duplicate. While createWorkspace is indeed dispatched twice—once from workspaceOpenedEvent and once from importWorkspaceAction—the reducer prevents duplicate entries from being added to Redux state, so no action is required here.

packages/bruno-electron/src/ipc/workspace.js (5)

4-5: LGTM!

Required dependencies properly imported.


248-306: LGTM with minor note on ignored directories.

Export handler is well-structured with proper cleanup. The ignoredDirectories check only matches top-level entries by name; nested node_modules within subdirectories will still be excluded since the recursive function checks each directory entry. Good compression level choice.


362-372: LGTM!

Workspace registration and watcher setup follow the established pattern from other handlers.


380-383: Good error cleanup.

Temp directory cleanup on error with .catch(() => {}) prevents cleanup failures from masking the original error.


321-322: Verify extract-zip handles zip slip attacks internally.

No published CVEs exist for extract-zip regarding zip slip vulnerabilities. Confirm the library validates entry paths before extraction, or add explicit path validation in the calling code if relying on untrusted archives.

packages/bruno-electron/package.json (1)

33-33: Dependencies archiver ^7.0.1 and extract-zip ^2.0.1 are current and secure. Both packages are at their latest versions with no known direct vulnerabilities. While maintenance activity is low for both, they remain stable, widely-used libraries appropriate for ZIP operations.

packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (2)

61-77: Solid file validation implementation.

Good defensive approach checking both file extension and MIME types, with optional chaining on window?.ipcRenderer?.getFilePath. Clear error messages for each failure case.


133-197: Well-structured modal with good UX.

The drag-drop zone with visual feedback, file display with remove option, and clear user guidance create a solid import experience.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (3)

28-28: Redundant validation check.

The .min(1, 'location is required') is redundant since .required('location is required') already validates non-empty strings.

Apply this diff:

-      workspaceLocation: Yup.string().min(1, 'location is required').required('location is required')
+      workspaceLocation: Yup.string().required('location is required')

105-116: Consider showing user feedback on browse failure.

When browseDirectory fails, the error is only logged to the console. Users won't see any feedback if the browse dialog encounters an issue.

Apply this diff to show a toast on error:

       .catch((error) => {
         formik.setFieldValue('workspaceLocation', '');
+        toast.error('Failed to browse directory');
         console.error(error);
       });

125-129: Simplify useEffect dependency array.

locationInputRef is a ref object that doesn't change between renders. Use an empty dependency array to clearly indicate this effect should only run on mount.

Apply this diff:

   useEffect(() => {
     if (locationInputRef && locationInputRef.current) {
       locationInputRef.current.focus();
     }
-  }, [locationInputRef]);
+  }, []);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf89d82 and 8dd746f.

📒 Files selected for processing (1)
  • packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (4)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3)
  • importWorkspaceAction (725-745)
  • importWorkspaceAction (725-745)
  • window (18-18)
packages/bruno-app/src/utils/common/error.js (1)
  • formatIpcError (38-44)
packages/bruno-app/src/components/Modal/index.js (1)
  • Modal (60-163)
packages/bruno-app/src/components/Help/index.js (1)
  • Help (11-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
🔇 Additional comments (3)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (3)

1-13: LGTM!

Imports are clean and properly organized.


133-237: LGTM!

The UI implementation is clean, properly structured, and follows accessibility best practices. The drag-and-drop zone with file picker provides a good UX, and the read-only location field with Browse button is intuitive.


61-77: No issues found. The getFilePath method is properly exposed in the preload script using Electron's standard webUtils.getPathForFile() API and is correctly available to the renderer process.

@bijin-bruno bijin-bruno merged commit 3cfbf89 into usebruno:main Dec 17, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants